diff options
author | Kai Vehmanen <first.surname@nokia.com> | 2007-06-19 08:06:00 +0000 |
---|---|---|
committer | Kai Vehmanen <first.surname@nokia.com> | 2007-06-19 08:06:00 +0000 |
commit | 181d9d56df9332544f72a856e51cff62f45a15ad (patch) | |
tree | 3d42a16ed0bd2ec9f98eac0645494110e1852adf /agent | |
parent | 99ff130b9bc44c75a30ee60078d1548d61f99270 (diff) | |
download | libnice-181d9d56df9332544f72a856e51cff62f45a15ad.tar.gz |
Major NICE agent update. Added supprt for peer-reflexive candidates, media keepalives, candidate keepalives, role conflict tie-breaking functionality, and for triggered checks. Added NICEAPI_EXPORT attributes to public functions. Includes numerous bugfixes to existing functionality.
darcs-hash:20070619080609-77cd4-d18bf44fe48a201e59556ae5a9dff2b5a2e7e073.gz
Diffstat (limited to 'agent')
-rw-r--r-- | agent/Makefile.am | 9 | ||||
-rw-r--r-- | agent/agent-priv.h | 10 | ||||
-rw-r--r-- | agent/agent.c | 537 | ||||
-rw-r--r-- | agent/agent.h | 23 | ||||
-rw-r--r-- | agent/candidate.c | 27 | ||||
-rw-r--r-- | agent/candidate.h | 7 | ||||
-rwxr-xr-x | agent/check-test-fullmode-with-stun.sh | 29 | ||||
-rw-r--r-- | agent/component.c | 9 | ||||
-rw-r--r-- | agent/component.h | 2 | ||||
-rw-r--r-- | agent/conncheck.c | 1187 | ||||
-rw-r--r-- | agent/conncheck.h | 24 | ||||
-rw-r--r-- | agent/discovery.c | 303 | ||||
-rw-r--r-- | agent/discovery.h | 21 | ||||
-rw-r--r-- | agent/stream.c | 8 | ||||
-rw-r--r-- | agent/stream.h | 17 | ||||
-rw-r--r-- | agent/test-add-remove-stream.c | 3 | ||||
-rw-r--r-- | agent/test-fullmode.c | 371 | ||||
-rw-r--r-- | agent/test-mainloop.c | 3 | ||||
-rw-r--r-- | agent/test-poll.c | 7 | ||||
-rw-r--r-- | agent/test-priority.c | 7 | ||||
-rw-r--r-- | agent/test-recv.c | 3 | ||||
-rw-r--r-- | agent/test-send.c | 3 | ||||
-rw-r--r-- | agent/test-stun.c | 5 | ||||
-rw-r--r-- | agent/test.c | 3 |
24 files changed, 1756 insertions, 862 deletions
diff --git a/agent/Makefile.am b/agent/Makefile.am index 823dd78..c5a29dd 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -27,7 +27,7 @@ noinst_LTLIBRARIES = libagent.la %-signals-marshal.c: %-signals-marshal.list Makefile.am glib-genmarshal --body --prefix=$(subst -,_,$*)_marshal $< > $@ sed -i "1i#include \"$*-signals-marshal.h\"" $@ - sed -ie 's/^}$$/(void)return_value;(void)invocation_hint;}/' $@ + sed -i -e 's/^}$$/(void)return_value;(void)invocation_hint;}/' $@ BUILT_SOURCES = \ agent-signals-marshal.h \ @@ -66,12 +66,15 @@ check_PROGRAMS = \ test-mainloop \ test-fullmode +check_SCRIPTS = \ + check-test-fullmode-with-stun.sh + # disabled test programs (using the old STUN API): -noinst_PROGRAMS = \ +EXTRA_PROGRAMS = \ test-stun \ test-send -TESTS = $(check_PROGRAMS) +TESTS = $(check_PROGRAMS) $(check_SCRIPTS) test_LDADD = $(COMMON_LDADD) diff --git a/agent/agent-priv.h b/agent/agent-priv.h index 63b88be..2ebdde8 100644 --- a/agent/agent-priv.h +++ b/agent/agent-priv.h @@ -49,6 +49,7 @@ #include "stream.h" #include "conncheck.h" +/** As specified in ICE spec ID-15 */ #define NICE_AGENT_TIMER_TA_DEFAULT 20 /* timer Ta, msecs */ #define NICE_AGENT_TIMER_TR_DEFAULT 15000 /* timer Tr, msecs */ @@ -59,6 +60,7 @@ struct _NiceAgent { GObject parent; /**< gobject pointer */ + gboolean full_mode; /**< property: full-mode */ NiceUDPSocketFactory *socket_factory; /**< property: socket factory */ GTimeVal next_check_tv; /**< property: next conncheck timestamp */ @@ -67,6 +69,8 @@ struct _NiceAgent gchar *turn_server_ip; /**< property: TURN server IP */ guint turn_server_port; /**< property: TURN server port */ gboolean controlling_mode; /**< property: controlling-mode */ + guint timer_ta; /**< property: timer Ta */ + GSList *local_addresses; /**< list of NiceAddresses for local interfaces */ GSList *streams; /**< list of Stream objects */ @@ -83,6 +87,10 @@ struct _NiceAgent GSList *conncheck_list; /**< list of CandidatePair items */ guint conncheck_timer_id; /**< id of discovery timer */ NiceCheckListState conncheck_state; /**< checklist state */ + guint keepalive_timer_id; /**< id of keepalive timer */ + guint64 tie_breaker; /**< tie breaker (ICE ID-16 sect + 5.2) */ + gchar ufragtmp[NICE_STREAM_MAX_UNAME]; /**< preallocated buffer for uname processing */ /* XXX: add pointer to internal data struct for ABI-safe extensions */ }; @@ -115,6 +123,8 @@ void agent_signal_new_candidate ( NiceAgent *agent, NiceCandidate *candidate); +void agent_signal_new_remote_candidate (NiceAgent *agent, NiceCandidate *candidate); + void agent_signal_initial_binding_request_received (NiceAgent *agent, Stream *stream); void agent_free_discovery_candidate_udp (gpointer data, gpointer user_data); diff --git a/agent/agent.c b/agent/agent.c index c32c9b5..e7b7858 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -36,15 +36,20 @@ * file under either the MPL or the LGPL. */ +/** + * @file agent.c + * @brief ICE agent API implementation + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + #include <string.h> #include <errno.h> #include <sys/select.h> #include <sys/socket.h> -#ifndef _BSD_SOURCE -#error "timercmp() macros needed" -#endif -#include <sys/time.h> /* timercmp() macro, BSD */ #include <netinet/in.h> #include <arpa/inet.h> @@ -75,7 +80,8 @@ enum PROP_TURN_SERVER, PROP_TURN_SERVER_PORT, PROP_CONTROLLING_MODE, - PROP_FULL_MODE + PROP_FULL_MODE, + PROP_STUN_PACING_TIMER }; @@ -85,6 +91,7 @@ enum SIGNAL_CANDIDATE_GATHERING_DONE, SIGNAL_NEW_SELECTED_PAIR, SIGNAL_NEW_CANDIDATE, + SIGNAL_NEW_REMOTE_CANDIDATE, SIGNAL_INITIAL_BINDING_REQUEST_RECEIVED, N_SIGNALS, }; @@ -168,11 +175,6 @@ nice_agent_class_init (NiceAgentClass *klass) /* install properties */ - /* XXX: add properties: - * - Ta-timer (construct-property, msec) - * - make the others construct-time only as well...? - */ - g_object_class_install_property (gobject_class, PROP_SOCKET_FACTORY, g_param_spec_pointer ( "socket-factory", @@ -194,10 +196,10 @@ nice_agent_class_init (NiceAgentClass *klass) "STUN server port", "The STUN server used to obtain server-reflexive candidates", 1, 65536, - IPPORT_STUN, /* default port */ + 1, /* not a construct property, ignored */ G_PARAM_READWRITE)); - g_object_class_install_property (gobject_class, PROP_STUN_SERVER, + g_object_class_install_property (gobject_class, PROP_TURN_SERVER, g_param_spec_string ( "turn-server", "TURN server", @@ -205,13 +207,13 @@ nice_agent_class_init (NiceAgentClass *klass) NULL, G_PARAM_READWRITE)); - g_object_class_install_property (gobject_class, PROP_STUN_SERVER_PORT, + g_object_class_install_property (gobject_class, PROP_TURN_SERVER_PORT, g_param_spec_uint ( "turn-server-port", "TURN server port", - "The STUN server used to obtain relay candidates", + "The TURN server used to obtain relay candidates", 1, 65536, - 3478, /* no default port for TURN, use the STUN default*/ + 1, /* not a construct property, ignored */ G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_CONTROLLING_MODE, @@ -230,6 +232,15 @@ nice_agent_class_init (NiceAgentClass *klass) TRUE, /* use full mode by default */ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, PROP_STUN_PACING_TIMER, + g_param_spec_uint ( + "stun-pacing-timer", + "STUN pacing timer", + "Timer 'Ta' (msecs) used in the IETF ICE specification for pacing candidate gathering and sending of connectivity checks", + 1, 0xffffffff, + NICE_AGENT_TIMER_TA_DEFAULT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + /* install signals */ /* signature: void cb(NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer self) */ @@ -292,6 +303,21 @@ nice_agent_class_init (NiceAgentClass *klass) G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_INVALID); + /* signature: void cb(NiceAgent *agent, guint stream_id, guint component_id, gchar *foundation) */ + signals[SIGNAL_NEW_REMOTE_CANDIDATE] = + g_signal_new ( + "new-remote-candidate", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, + NULL, + agent_marshal_VOID__UINT_UINT_STRING, + G_TYPE_NONE, + 3, + G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, + G_TYPE_INVALID); + /* signature: void cb(NiceAgent *agent, guint stream_id, gpointer self) */ signals[SIGNAL_INITIAL_BINDING_REQUEST_RECEIVED] = g_signal_new ( @@ -309,6 +335,10 @@ nice_agent_class_init (NiceAgentClass *klass) } +static void priv_generate_tie_breaker (NiceAgent *agent) +{ + nice_rng_generate_bytes (agent->rng, 8, (gchar*)&agent->tie_breaker); +} static void nice_agent_init (NiceAgent *agent) @@ -327,8 +357,10 @@ nice_agent_init (NiceAgent *agent) agent->discovery_unsched_items = 0; agent->discovery_timer_id = 0; agent->conncheck_timer_id = 0; + agent->keepalive_timer_id = 0; agent->rng = nice_rng_new (); + priv_generate_tie_breaker (agent); } @@ -340,7 +372,7 @@ nice_agent_init (NiceAgent *agent) * * Returns: the new agent **/ -NiceAgent * +NICEAPI_EXPORT NiceAgent * nice_agent_new (NiceUDPSocketFactory *factory) { return g_object_new (NICE_TYPE_AGENT, @@ -388,6 +420,10 @@ nice_agent_get_property ( g_value_set_boolean (value, agent->full_mode); break; + case PROP_STUN_PACING_TIMER: + g_value_set_uint (value, agent->timer_ta); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -429,6 +465,10 @@ nice_agent_set_property ( agent->full_mode = g_value_get_boolean (value); break; + case PROP_STUN_PACING_TIMER: + agent->timer_ta = g_value_get_uint (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -467,6 +507,14 @@ void agent_signal_new_candidate (NiceAgent *agent, NiceCandidate *candidate) candidate->foundation); } +void agent_signal_new_remote_candidate (NiceAgent *agent, NiceCandidate *candidate) +{ + g_signal_emit (agent, signals[SIGNAL_NEW_REMOTE_CANDIDATE], 0, + candidate->stream_id, + candidate->component_id, + candidate->foundation); +} + void agent_signal_component_state_change (NiceAgent *agent, guint stream_id, guint component_id, NiceComponentState state) { Component *component; @@ -508,33 +556,43 @@ static void priv_signal_component_state_connecting (NiceAgent *agent, guint stre * * Add a data stream to @agent. * + * @pre local addresses must be set with nice_agent_add_local_address() + * * Returns: the ID of the new stream, 0 on failure **/ -guint +NICEAPI_EXPORT guint nice_agent_add_stream ( NiceAgent *agent, guint n_components) { Stream *stream; - GSList *i; + GSList *i, *modified_list = NULL; g_assert (n_components == 1); - /* XXX: make memory-allocation safe */ - - if (!agent->streams) { - /* note: this contains a Y2038 issue */ - agent->next_check_tv.tv_sec = - agent->next_check_tv.tv_usec = (long)-1; - } + if (!agent->local_addresses) + return 0; stream = stream_new (); - stream->id = agent->next_stream_id++; - /* note: generate ufrag/pwd for the stream (see ICE ID-15 15.4) */ - nice_rng_generate_bytes_print (agent->rng, NICE_STREAM_MAX_UFRAG_LEN, stream->local_ufrag); - nice_rng_generate_bytes_print (agent->rng, NICE_STREAM_MAX_PWD_LEN, stream->local_password); + if (stream) { + modified_list = g_slist_append (agent->streams, stream); + if (modified_list) { + stream->id = agent->next_stream_id++; + g_debug ("allocating stream id %u (%p)", stream->id, stream); + + /* note: generate ufrag/pwd for the stream (see ICE ID-15 15.4) */ + nice_rng_generate_bytes_print (agent->rng, NICE_STREAM_DEF_UFRAG - 1, stream->local_ufrag); + nice_rng_generate_bytes_print (agent->rng, NICE_STREAM_DEF_PWD - 1, stream->local_password); + + agent->streams = modified_list; + } + else + stream_free (stream); + } - agent->streams = g_slist_append (agent->streams, stream); + /* note: error in allocating objects */ + if (!modified_list) + return 0; g_debug ("In %s mode, starting candidate gathering.", agent->full_mode ? "ICE-FULL" : "ICE-LITE"); @@ -550,27 +608,40 @@ nice_agent_add_stream ( host_candidate = discovery_add_local_host_candidate (agent, stream->id, stream->component->id, addr); - if (host_candidate && - agent->full_mode && + if (!host_candidate) { + stream->id = 0; + break; + } + + if (agent->full_mode && agent->stun_server_ip) { - /* XXX: need to check for redundant candidates? -> not yet, - * this is done later on */ + /* note: no need to check for redundant candidates, as this is + * done later on in the process */ cand = g_slice_new0 (CandidateDiscovery); if (cand) { - cand->type = NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE; - cand->socket = host_candidate->sockptr->fileno; - cand->nicesock = host_candidate->sockptr; - cand->server_addr = agent->stun_server_ip; - cand->interface = addr; - cand->stream = stream; - /* XXX: not multi-component ready */ - cand->component = stream->component; - cand->agent = agent; - g_debug ("Adding new srv-rflx candidate %p\n", cand); - agent->discovery_list = g_slist_append (agent->discovery_list, cand); - ++agent->discovery_unsched_items; + modified_list = g_slist_append (agent->discovery_list, cand); + + if (modified_list) { + agent->discovery_list = modified_list; + cand->type = NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE; + cand->socket = host_candidate->sockptr->fileno; + cand->nicesock = host_candidate->sockptr; + cand->server_addr = agent->stun_server_ip; + cand->server_port = agent->stun_server_port; + cand->interface = addr; + cand->stream = stream; + /* XXX: not multi-component ready */ + cand->component = stream->component; + cand->agent = agent; + g_debug ("Adding new srv-rflx candidate %p\n", cand); + modified_list = g_slist_append (agent->discovery_list, cand); + if (modified_list) { + agent->discovery_list = modified_list; + ++agent->discovery_unsched_items; + } + } } else { /* note: memory allocation failure, return error */ @@ -582,24 +653,34 @@ nice_agent_add_stream ( /* step: attach the newly created sockets to the mainloop * context */ - if (agent->main_context_set) + if (agent->main_context_set && stream->id > 0) priv_attach_new_stream (agent, stream); /* note: no async discoveries pending, signal that we are ready */ if (agent->discovery_unsched_items == 0) agent_signal_gathering_done (agent); - else if (agent->discovery_list) + else { + g_assert (agent->discovery_list); discovery_schedule (agent); + } return stream->id; } +static void priv_remove_keepalive_timer (NiceAgent *agent) +{ + if (agent->keepalive_timer_id) { + g_source_remove (agent->keepalive_timer_id), + agent->keepalive_timer_id = 0; + } +} + /** * nice_agent_remove_stream: * @agent: a NiceAgent * @stream_id: the ID of the stream to remove **/ -void +NICEAPI_EXPORT void nice_agent_remove_stream ( NiceAgent *agent, guint stream_id) @@ -619,8 +700,11 @@ nice_agent_remove_stream ( /* remove the stream itself */ priv_deattach_stream (stream); - stream_free (stream); agent->streams = g_slist_remove (agent->streams, stream); + stream_free (stream); + + if (!agent->streams) + priv_remove_keepalive_timer (agent); } /** @@ -628,25 +712,28 @@ nice_agent_remove_stream ( * @agent: A NiceAgent * @addr: the address of a local IP interface * - * Inform the agent of the presence of an address that a local network - * interface is bound to. + * Inform the agent of the presence of an address that a local + * network interface is bound to. + * + * @return FALSE on fatal (memory allocation) errors **/ -void +NICEAPI_EXPORT gboolean nice_agent_add_local_address (NiceAgent *agent, NiceAddress *addr) { NiceAddress *dup; + GSList *modified_list; dup = nice_address_dup (addr); dup->port = 0; - agent->local_addresses = g_slist_append (agent->local_addresses, dup); - - /* XXX: Should we generate local candidates for existing streams at this - * point, or require that local addresses are set before media streams are - * added? - */ + modified_list = g_slist_append (agent->local_addresses, dup); + if (modified_list) { + agent->local_addresses = modified_list; + return TRUE; + } + return FALSE; } -static void priv_add_remote_candidate ( +static gboolean priv_add_remote_candidate ( NiceAgent *agent, guint stream_id, guint component_id, @@ -661,41 +748,70 @@ static void priv_add_remote_candidate ( { Component *component; NiceCandidate *candidate; - - /* XXX: dear compiler, these are for you: */ - (void)username; (void)password; (void)priority; + gchar *username_dup = NULL, *password_dup = NULL; + gboolean error_flag = FALSE; if (!agent_find_component (agent, stream_id, component_id, NULL, &component)) - return; - - candidate = nice_candidate_new (type); - - candidate->stream_id = stream_id; - candidate->component_id = component_id; + return FALSE; - candidate->type = type; - if (addr) - candidate->addr = *addr; - if (related_addr) - candidate->base_addr = *related_addr; + if (username) + username_dup = g_strdup (username); + if (password) + password_dup = g_strdup (password); - candidate->transport = transport; + candidate = nice_candidate_new (type); + if (candidate) { + GSList *modified_list = g_slist_append (component->remote_candidates, candidate); + if (modified_list) { + component->remote_candidates = modified_list; + + candidate->stream_id = stream_id; + candidate->component_id = component_id; - if (username) - candidate->username = g_strdup (username); - if (password) - candidate->password = g_strdup (password); + candidate->type = type; + if (addr) + candidate->addr = *addr; +#ifndef NDEBUG + { + gchar tmpbuf[INET6_ADDRSTRLEN]; + nice_address_to_string ((NiceAddress *)addr, tmpbuf); + g_debug ("Adding remote candidate with addr %s:%u.", tmpbuf, addr->port); + } +#endif + + if (related_addr) + candidate->base_addr = *related_addr; + + candidate->transport = transport; + candidate->priority = priority; + candidate->username = username_dup; + candidate->password = password_dup; + + if (foundation) + g_strlcpy (candidate->foundation, foundation, NICE_CANDIDATE_MAX_FOUNDATION); - if (foundation) - candidate->foundation = g_strdup (foundation); + /* XXX: may be called before candidate-gathering-done is signalled, + * make sure this is handled correctly! */ - component->remote_candidates = g_slist_append (component->remote_candidates, - candidate); + if (conn_check_add_for_candidate (agent, stream_id, component, candidate) < 0) + error_flag = TRUE; + } + else /* memory alloc error: list insert */ + error_flag = TRUE; + } + else /* memory alloc error: candidate creation */ + error_flag = TRUE; + - /* XXX: may be called before candidate-gathering-done is signalled, - * make sure this is handled correctly! */ + if (error_flag) { + if (candidate) + nice_candidate_free (candidate); + g_free (username_dup); + g_free (password_dup); + return FALSE; + } - conn_check_add_for_candidate (agent, stream_id, component, candidate); + return TRUE; } /** @@ -711,7 +827,7 @@ static void priv_add_remote_candidate ( * * @return TRUE on success */ -gboolean +NICEAPI_EXPORT gboolean nice_agent_set_remote_credentials ( NiceAgent *agent, guint stream_id, @@ -723,8 +839,8 @@ nice_agent_set_remote_credentials ( /* note: oddly enough, ufrag and pwd can be empty strings */ if (stream && ufrag && pwd) { - strncpy (stream->remote_ufrag, ufrag, NICE_STREAM_MAX_UFRAG_LEN); - strncpy (stream->remote_password, pwd, NICE_STREAM_MAX_PWD_LEN); + g_strlcpy (stream->remote_ufrag, ufrag, NICE_STREAM_MAX_UFRAG); + g_strlcpy (stream->remote_password, pwd, NICE_STREAM_MAX_PWD); return TRUE; } @@ -744,7 +860,7 @@ nice_agent_set_remote_credentials ( * * @return TRUE on success */ -gboolean +NICEAPI_EXPORT gboolean nice_agent_get_local_credentials ( NiceAgent *agent, guint stream_id, @@ -776,8 +892,12 @@ nice_agent_get_local_credentials ( * @password: the new candidate's password (XXX: candidates don't have usernames) * * Add a candidate our peer has informed us about to the agent's list. + * + * Note: NICE_AGENT_MAX_REMOTE_CANDIDATES is the absolute + * maximum limit for remote candidates + * @return FALSE on fatal (memory alloc) errors **/ -void +NICEAPI_EXPORT gboolean nice_agent_add_remote_candidate ( NiceAgent *agent, guint stream_id, @@ -787,19 +907,20 @@ nice_agent_add_remote_candidate ( const gchar *username, const gchar *password) { - priv_add_remote_candidate (agent, - stream_id, - component_id, - type, - addr, - NULL, - NICE_CANDIDATE_TRANSPORT_UDP, - 0, - username, - password, - NULL); - - /* later: for each component, generate a new check with the new + return + priv_add_remote_candidate (agent, + stream_id, + component_id, + type, + addr, + NULL, + NICE_CANDIDATE_TRANSPORT_UDP, + 0, + username, + password, + NULL); + + /* XXX/later: for each component, generate a new check with the new candidate, see below set_remote_candidates() */ } @@ -812,51 +933,55 @@ nice_agent_add_remote_candidate ( * * Sets the remote candidates for a component of a stream. Replaces * any existing remote candidates. + * + * Note: NICE_AGENT_MAX_REMOTE_CANDIDATES is the absolute + * maximum limit for remote candidates + * + * @return number of candidates added, negative on fatal (memory + * allocs) errors **/ -void -nice_agent_set_remote_candidates (NiceAgent *agent, guint stream_id, guint component_id, GSList *candidates) +NICEAPI_EXPORT int +nice_agent_set_remote_candidates (NiceAgent *agent, guint stream_id, guint component_id, const GSList *candidates) { - GSList *i; + const GSList *i; + int added = 0; /* XXX: clean up existing remote candidates, and abort any * connectivity checks using these candidates */ - for (i = candidates; i; i = i->next) { + for (i = candidates; i && added >= 0; i = i->next) { NiceCandidateDesc *d = (NiceCandidateDesc*) i->data; - priv_add_remote_candidate (agent, - stream_id, - component_id, - d->type, - d->addr, - d->related_addr, - d->transport, - d->priority, - NULL, - NULL, - d->foundation); + gboolean res = + priv_add_remote_candidate (agent, + stream_id, + component_id, + d->type, + d->addr, + d->related_addr, + d->transport, + d->priority, + NULL, + NULL, + d->foundation); + if (res) + ++added; + else + added = -1; } - - conn_check_schedule_next (agent); -} - -#if 0 -static NiceCandidate * -_local_candidate_lookup (NiceAgent *agent, guint candidate_id) -{ - GSList *i; - for (i = agent->local_candidates; i; i = i->next) - { - NiceCandidate *c = i->data; - - if (c->id == candidate_id) - return c; - } + if (added > 0) + conn_check_schedule_next (agent); - return NULL; + return added; } -#endif + +/** + * Reads data from a ready, nonblocking socket attached to an ICE + * stream component. + * + * @return number of octets received, or zero on error + */ static guint _nice_agent_recv ( NiceAgent *agent, @@ -869,10 +994,16 @@ _nice_agent_recv ( NiceAddress from; guint len; - g_debug ("Packet received on local socket %d.", udp_socket->fileno); - len = nice_udp_socket_recv (udp_socket, &from, - buf_len, buf); + buf_len, buf); + +#ifndef NDEBUG + { + gchar tmpbuf[INET6_ADDRSTRLEN]; + nice_address_to_string (&from, tmpbuf); + g_debug ("Packet received on local socket %u from %s:%u (%u octets).", udp_socket->fileno, tmpbuf, from.port, len); + } +#endif if (len == 0) return 0; @@ -884,32 +1015,11 @@ _nice_agent_recv ( return 0; } - /* XXX: verify sender; maybe: - * - * if (candidate->other != NULL) - * { - * if (from != candidate->other.addr) - * // ignore packet from unexpected sender - * return; - * } - * else - * { - * // go through remote candidates, looking for one matching packet from - * // address; if found, assign it to candidate->other and call handler, - * // otherwise ignore it - * } + /* step: check for a RTP fingerprint * - * Perhaps remote socket affinity is superfluous and all we need is the - * second part. - * Perhaps we should also check whether this candidate is supposed to be - * active. - */ - - /* The top two bits of an RTP message are the version number; the current + * The top two bits of an RTP message are the version number; the current * version number is 2. The top two bits of a STUN message are always 0. - */ - - /* step: check for a RTP fingerprint + * * - XXX: should use a two-phase check, first a lightweight check, * and then full validation */ if ((buf[0] & 0xc0) == 0x80) @@ -918,10 +1028,10 @@ _nice_agent_recv ( return len; } /* step: validate using the new STUN API */ - /* - XXX: old check '((buf[0] & 0xc0) == 0)' */ + /* - note: old check '((buf[0] & 0xc0) == 0)' */ else if (stun_validate ((uint8_t*)buf, len) > 0) { - conn_check_handle_inbound_stun (agent, stream, component, &from, buf, len); + conn_check_handle_inbound_stun (agent, stream, component, udp_socket, &from, buf, len); } else { @@ -929,33 +1039,9 @@ _nice_agent_recv ( return len; } - /* code using the old SUTN API */ -#if 0 - { - /* looks like a STUN message (connectivity check) */ - /* connectivity checks are described in ICE-13 ยง7. */ - - /* XXX: still using the old STUN API below - * - with new API, call stun_bind_process() or some such - * to process the incoming STUN packet */ - - StunMessage *msg; - - msg = stun_message_unpack (len, buf); - - if (msg != NULL) - { - conn_check_handle_inbound_stun_old (agent, stream, component, candidate, from, msg); - stun_message_free (msg); - } - } -#endif - - /* anything else is ignored */ return 0; } - /** * nice_agent_recv: * @agent: a NiceAgent @@ -968,7 +1054,7 @@ _nice_agent_recv ( * * Returns: the amount of data read into @buf **/ -guint +NICEAPI_EXPORT guint nice_agent_recv ( NiceAgent *agent, guint stream_id, @@ -1027,11 +1113,12 @@ nice_agent_recv ( } } - g_assert_not_reached (); + /* note: commented out to avoid compiler warnings + * + * g_assert_not_reached (); */ } - -guint +NICEAPI_EXPORT guint nice_agent_recv_sock ( NiceAgent *agent, guint stream_id, @@ -1068,7 +1155,7 @@ nice_agent_recv_sock ( * * Returns: A list of file descriptors from @other_fds that are readable **/ -GSList * +NICEAPI_EXPORT GSList * nice_agent_poll_read ( NiceAgent *agent, GSList *other_fds, @@ -1118,11 +1205,17 @@ nice_agent_poll_read ( for (j = 0; j <= max_fd; j++) if (FD_ISSET (j, &fds)) { - if (g_slist_find (other_fds, GUINT_TO_POINTER (j))) - ret = g_slist_append (ret, GUINT_TO_POINTER (j)); + if (g_slist_find (other_fds, GUINT_TO_POINTER (j))) { + GSList *modified_list = g_slist_append (ret, GUINT_TO_POINTER (j)); + if (modified_list == NULL) { + g_slist_free (ret); + return NULL; + } + ret = modified_list; + } else { - NiceUDPSocket *socket; + NiceUDPSocket *socket = NULL; Stream *stream = NULL; gchar buf[MAX_STUN_DATAGRAM_PAYLOAD]; guint len; @@ -1157,7 +1250,15 @@ nice_agent_poll_read ( } -void + +/** + * Sends a data payload over a stream component. + * + * @pre component state MUST be NICE_COMPONENT_STATE_READY + * + * @return number of bytes sent, or negative error code + */ +NICEAPI_EXPORT gint nice_agent_send ( NiceAgent *agent, guint stream_id, @@ -1168,7 +1269,7 @@ nice_agent_send ( Stream *stream; Component *component; - /* XXX: dear compiler, these are for you: */ + /* note: dear compiler, these are for you: */ (void)component_id; stream = agent_find_stream (agent, stream_id); @@ -1181,15 +1282,19 @@ nice_agent_send ( NiceUDPSocket *sock; NiceAddress *addr; -#if 1 +#ifndef NDEBUG g_debug ("s%d:%d: sending %d bytes to %08x:%d", stream_id, component_id, - len, component->selected_pair.remote->addr.addr_ipv4, component->selected_pair.remote->addr.port); + len, component->selected_pair.remote->addr.addr.addr_ipv4, component->selected_pair.remote->addr.port); #endif sock = component->selected_pair.local->sockptr; addr = &component->selected_pair.remote->addr; nice_udp_socket_send (sock, addr, len, buf); + component->media_after_tick = TRUE; + return len; } + + return -1; } @@ -1201,9 +1306,9 @@ nice_agent_send ( * it. To get full results, the client should wait for the * 'candidates-gathering-done' signal. * - * Returns: a GSList of local candidates belonging to @agent + * Returns: a GSList of local candidates (NiceCandidate) belonging to @agent **/ -GSList * +NICEAPI_EXPORT GSList * nice_agent_get_local_candidates ( NiceAgent *agent, guint stream_id, @@ -1225,9 +1330,13 @@ nice_agent_get_local_candidates ( * The caller owns the returned GSList but not the candidates contained within * it. * - * Returns: a GSList of remote candidates belonging to @agent + * Note: the list of remote candidates can change during processing. + * The client should register for the "new-remote-candidate" signal to + * get notification of new remote candidates. + * + * Returns: a GSList of remote candidates (NiceCandidate) belonging to @agent **/ -GSList * +NICEAPI_EXPORT GSList * nice_agent_get_remote_candidates ( NiceAgent *agent, guint stream_id, @@ -1244,7 +1353,6 @@ nice_agent_get_remote_candidates ( return g_slist_copy (component->remote_candidates); } - static void nice_agent_dispose (GObject *object) { @@ -1259,6 +1367,8 @@ nice_agent_dispose (GObject *object) conn_check_free (agent); g_assert (agent->conncheck_list == NULL); + priv_remove_keepalive_timer (agent); + for (i = agent->local_addresses; i; i = i->next) { NiceAddress *a = i->data; @@ -1345,7 +1455,7 @@ nice_agent_g_source_cb ( gchar buf[MAX_STUN_DATAGRAM_PAYLOAD]; guint len; - /* XXX: dear compiler, these are for you: */ + /* note: dear compiler, these are for you: */ (void)source; len = _nice_agent_recv (agent, stream, component, ctx->socket, @@ -1375,6 +1485,7 @@ static gboolean priv_attach_new_stream (NiceAgent *agent, Stream *stream) GIOChannel *io; GSource *source; IOCtx *ctx; + GSList *modified_list; io = g_io_channel_unix_new (udp_socket->fileno); source = g_io_create_watch (io, G_IO_IN); @@ -1383,11 +1494,12 @@ static gboolean priv_attach_new_stream (NiceAgent *agent, Stream *stream) ctx, (GDestroyNotify) io_ctx_free); g_debug ("Attach source %p (stream %u).", source, stream->id); g_source_attach (source, NULL); - component->gsources = g_slist_append (component->gsources, source); - if (!component->gsources) { + modified_list = g_slist_append (component->gsources, source); + if (!modified_list) { g_source_destroy (source); return FALSE; } + component->gsources = modified_list; } return TRUE; @@ -1415,7 +1527,7 @@ static void priv_deattach_stream (Stream *stream) component->gsources = NULL; } -gboolean +NICEAPI_EXPORT gboolean nice_agent_main_context_attach ( NiceAgent *agent, GMainContext *ctx, @@ -1427,9 +1539,6 @@ nice_agent_main_context_attach ( if (agent->main_context_set) return FALSE; - /* XXX: when sockets are not yet created, or new streams are added, - * the mainloop integration won't then work anymore! */ - /* attach candidates */ for (i = agent->streams; i; i = i->next) { diff --git a/agent/agent.h b/agent/agent.h index ece9114..4ffe4f9 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -70,6 +70,13 @@ G_BEGIN_DECLS (G_TYPE_INSTANCE_GET_CLASS ((obj), \ NICE_TYPE_AGENT, NiceAgentClass)) +/** + * A hard limit for number for remote candidates. This + * limit is enforced to protect against malevolent remote + * clients. + */ +#define NICE_AGENT_MAX_REMOTE_CANDIDATES 25 + typedef enum { NICE_COMPONENT_STATE_DISCONNECTED, /* no activity scheduled */ @@ -92,13 +99,13 @@ typedef enum typedef struct _NiceCandidateDesc NiceCandidateDesc; struct _NiceCandidateDesc { - const gchar *foundation; + gchar *foundation; guint component_id; NiceCandidateTransport transport; guint32 priority; - const NiceAddress *addr; + NiceAddress *addr; NiceCandidateType type; - const NiceAddress *related_addr; /* optional */ + NiceAddress *related_addr; /* optional */ }; typedef struct _NiceAgent NiceAgent; @@ -120,7 +127,7 @@ GType nice_agent_get_type (void); NiceAgent * nice_agent_new (NiceUDPSocketFactory *factory); -void +gboolean nice_agent_add_local_address (NiceAgent *agent, NiceAddress *addr); guint @@ -145,7 +152,7 @@ nice_agent_get_local_credentials ( guint stream_id, const gchar **ufrag, const gchar **pwd); -void +gboolean nice_agent_add_remote_candidate ( NiceAgent *agent, guint stream_id, @@ -155,12 +162,12 @@ nice_agent_add_remote_candidate ( const gchar *username, const gchar *password); -void +int nice_agent_set_remote_candidates ( NiceAgent *agent, guint stream_id, guint component_id, - GSList *candidates); + const GSList *candidates); guint nice_agent_recv ( @@ -186,7 +193,7 @@ nice_agent_poll_read ( NiceAgentRecvFunc func, gpointer data); -void +gint nice_agent_send ( NiceAgent *agent, guint stream_id, diff --git a/agent/candidate.c b/agent/candidate.c index 29839cb..696c03d 100644 --- a/agent/candidate.c +++ b/agent/candidate.c @@ -36,6 +36,15 @@ * file under either the MPL or the LGPL. */ +/** + * @file candidate.c + * @brief ICE candidate functions + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + #include "agent.h" #include "component.h" @@ -44,7 +53,7 @@ * candidates, server reflexive candidates, and relayed candidates. */ -NiceCandidate * +NICEAPI_EXPORT NiceCandidate * nice_candidate_new (NiceCandidateType type) { NiceCandidate *candidate; @@ -55,28 +64,22 @@ nice_candidate_new (NiceCandidateType type) } -void +NICEAPI_EXPORT void nice_candidate_free (NiceCandidate *candidate) { /* better way of checking if socket is allocated? */ - if (candidate->source) - g_source_destroy (candidate->source); - if (candidate->username) g_free (candidate->username); if (candidate->password) g_free (candidate->password); - if (candidate->foundation) - g_free (candidate->foundation); - g_slice_free (NiceCandidate, candidate); } -gfloat +NICEAPI_EXPORT gfloat nice_candidate_jingle_priority (NiceCandidate *candidate) { switch (candidate->type) @@ -94,7 +97,7 @@ nice_candidate_jingle_priority (NiceCandidate *candidate) /* ICE-15 ยง4.1.2.1; returns number between 1 and 0x7effffff */ G_GNUC_CONST -guint32 +NICEAPI_EXPORT guint32 nice_candidate_ice_priority_full ( // must be โ (0, 126) (max 2^7 - 2) guint type_preference, @@ -111,7 +114,7 @@ nice_candidate_ice_priority_full ( G_GNUC_CONST -guint32 +NICEAPI_EXPORT guint32 nice_candidate_ice_priority (const NiceCandidate *candidate) { guint8 type_preference = 0; @@ -135,7 +138,7 @@ nice_candidate_ice_priority (const NiceCandidate *candidate) /** * Calculates the pair priority as specified in ICE -15 spec 5.7.2. */ -guint64 +NICEAPI_EXPORT guint64 nice_candidate_pair_priority (guint32 o_prio, guint32 a_prio) { guint32 max = o_prio > a_prio ? o_prio : a_prio; diff --git a/agent/candidate.h b/agent/candidate.h index ac5c21e..c939d77 100644 --- a/agent/candidate.h +++ b/agent/candidate.h @@ -48,6 +48,8 @@ G_BEGIN_DECLS #define NICE_CANDIDATE_TYPE_PREF_SERVER_REFLEXIVE 100 #define NICE_CANDIDATE_TYPE_PREF_RELAYED 60 +#define NICE_CANDIDATE_MAX_FOUNDATION 16 + typedef enum { NICE_CANDIDATE_TYPE_HOST, @@ -72,11 +74,10 @@ struct _NiceCandidate guint32 priority; guint stream_id; guint component_id; - gchar *foundation; - NiceUDPSocket *sockptr; /* XXX: to replace 'sock', see comment above */ + gchar foundation[NICE_CANDIDATE_MAX_FOUNDATION]; + NiceUDPSocket *sockptr; gchar *username; /* pointer to a NULL-terminated username string */ gchar *password; /* pointer to a NULL-terminated password string */ - GSource *source; }; diff --git a/agent/check-test-fullmode-with-stun.sh b/agent/check-test-fullmode-with-stun.sh new file mode 100755 index 0000000..90bae7b --- /dev/null +++ b/agent/check-test-fullmode-with-stun.sh @@ -0,0 +1,29 @@ +#! /bin/sh + +STUND=../stun/stund + +echo "Starting ICE full-mode with STUN unit test." + +[ -e "$STUND" ] || { + echo "STUN server not found: Cannot run unit test!" >&2 + exit 77 +} + +set -x +pidfile=./stund.pid + +export NICE_STUN_SERVER=127.0.0.1 +export NICE_STUN_SERVER_PORT=3800 + +echo "Launching stund on port ${NICE_STUN_SERVER_PORT}." + +rm -f -- "$pidfile" +(sh -c "echo \$\$ > \"$pidfile\" && exec ../stun/stund ${NICE_STUN_SERVER_PORT}") & +sleep 1 + +./test-fullmode +error=$? + +kill -- "$(cat "$pidfile")" +wait +exit ${error} diff --git a/agent/component.c b/agent/component.c index 5450744..dd02c3a 100644 --- a/agent/component.c +++ b/agent/component.c @@ -35,6 +35,15 @@ * file under either the MPL or the LGPL. */ +/** + * @file component.c + * @brief ICE component functions + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + #include "component.h" Component * diff --git a/agent/component.h b/agent/component.h index 4fa38ce..84fa3b8 100644 --- a/agent/component.h +++ b/agent/component.h @@ -80,6 +80,8 @@ struct _Component GSList *gsources; /**< list of GSource objs */ CandidatePair selected_pair; /**< independent from checklists, see ICE 11.1.1 (ID-15) */ + gboolean media_after_tick; /**< true if media received since last + keepalive tick */ /* XXX: **to be removed** * --cut-- diff --git a/agent/conncheck.c b/agent/conncheck.c index 7f37b1c..df7a9ae 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -36,22 +36,33 @@ * file under either the MPL or the LGPL. */ +/** + * @file conncheck.c + * @brief ICE connectivity checks + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + #include <errno.h> #include <string.h> #include <glib.h> -#ifndef _BSD_SOURCE -#error "timercmp() macros needed" -#endif -#include <sys/time.h> /* timercmp() macro, BSD */ - #include "agent.h" #include "agent-priv.h" #include "conncheck.h" #include "discovery.h" #include "stun.h" +#include "stun-msg.h" +static inline int priv_timer_expired (GTimeVal *restrict timer, GTimeVal *restrict now) +{ + return (now->tv_sec == timer->tv_sec) ? + now->tv_usec >= timer->tv_usec : + now->tv_sec >= timer->tv_sec; +} static void priv_update_check_list_state (NiceAgent *agent, Stream *stream); @@ -62,8 +73,8 @@ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check { GSList *i; - /* XXX: should return the highest priority check on the - * waiting list! */ + /* note: list is sorted in priority order to first waiting check has + * the highest priority */ for (i = conn_check_list; i ; i = i->next) { CandidateCheckPair *p = i->data; @@ -82,7 +93,7 @@ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair *pair) { g_get_current_time (&pair->next_tick); - g_time_val_add (&pair->next_tick, 6000); /* XXX: 600msec */ + g_time_val_add (&pair->next_tick, agent->timer_ta * 10); pair->state = NICE_CHECK_IN_PROGRESS; conn_check_send (agent, pair); return TRUE; @@ -101,6 +112,9 @@ static gboolean priv_conn_check_unfreeze_next (GSList *conncheck_list) GSList *i; int c; + /* note: the pair list is already sorted so separate prio check + * is not needed */ + for (c = NICE_COMPONENT_TYPE_RTP; (pair == NULL) && c < NICE_COMPONENT_TYPE_RTCP; c++) { for (i = conncheck_list; i ; i = i->next) { CandidateCheckPair *p = i->data; @@ -128,7 +142,6 @@ static gboolean priv_conn_check_unfreeze_next (GSList *conncheck_list) * This function is designed for the g_timeout_add() interface. * * @return will return FALSE when no more pending timers. - */ static gboolean priv_conn_check_tick (gpointer pointer) { @@ -136,9 +149,12 @@ static gboolean priv_conn_check_tick (gpointer pointer) NiceAgent *agent = pointer; GSList *i; gboolean keep_timer_going = FALSE; + GTimeVal now; + int frozen = 0, inprogress = 0, waiting = 0, succeeded = 0, nominated = 0; + pair = priv_conn_check_find_next_waiting (agent->conncheck_list); -#ifdef DEBUG +#ifndef NDEBUG { static int tick_counter = 0; if (++tick_counter % 20 == 0) @@ -158,94 +174,251 @@ static gboolean priv_conn_check_tick (gpointer pointer) keep_timer_going = TRUE; } - { - GTimeVal now; - int frozen = 0, inprogress = 0, waiting = 0; - - g_get_current_time (&now); - - for (i = agent->conncheck_list; i ; i = i->next) { - CandidateCheckPair *p = i->data; + g_get_current_time (&now); - if (p->state == NICE_CHECK_IN_PROGRESS) { - /* note: macro from sys/time.h but compatible with GTimeVal */ - if (p->stun_ctx == NULL) { - g_debug ("STUN connectivity check was cancelled, marking as done."); - p->state = NICE_CHECK_FAILED; - } - else if (timercmp(&p->next_tick, &now, <=)) { - int res = stun_bind_elapse (p->stun_ctx); - if (res == EAGAIN) { - /* case: not ready complete, so schedule next timeout */ - unsigned int timeout = stun_bind_timeout (p->stun_ctx); + for (i = agent->conncheck_list; i ; i = i->next) { + CandidateCheckPair *p = i->data; + + if (p->state == NICE_CHECK_IN_PROGRESS) { + /* note: macro from sys/time.h but compatible with GTimeVal */ + if (p->stun_ctx == NULL) { + g_debug ("STUN connectivity check was cancelled, marking as done."); + p->state = NICE_CHECK_FAILED; + } + else if (priv_timer_expired (&p->next_tick, &now)) { + int res = stun_bind_elapse (p->stun_ctx); + if (res == EAGAIN) { + /* case: not ready, so schedule a new timeout */ + unsigned int timeout = stun_bind_timeout (p->stun_ctx); - /* note: convert from milli to microseconds for g_time_val_add() */ - g_get_current_time (&p->next_tick); - g_time_val_add (&p->next_tick, timeout * 10); - - keep_timer_going = TRUE; - } - else { - /* case: error, abort processing */ - g_debug ("Retransmissions failed, giving up on connectivity check %p", p); - p->state = NICE_CHECK_FAILED; - } + /* note: convert from milli to microseconds for g_time_val_add() */ + g_get_current_time (&p->next_tick); + g_time_val_add (&p->next_tick, timeout * 10); + + keep_timer_going = TRUE; + p->traffic_after_tick = TRUE; /* for keepalive timer */ + } + else { + /* case: error, abort processing */ + g_debug ("Retransmissions failed, giving up on connectivity check %p", p); + p->state = NICE_CHECK_FAILED; + p->stun_ctx = NULL; } } - - if (p->state == NICE_CHECK_FROZEN) - ++frozen; - if (p->state == NICE_CHECK_IN_PROGRESS) - ++inprogress; - else if (p->state == NICE_CHECK_WAITING) - ++waiting; - } - -#ifdef DEBUG - { - static int tick_counter = 0; - if (++tick_counter % 20 == 0 || keep_timer_going != TRUE) - g_debug ("timer: %d frozen, %d in-progress, %d waiting.", frozen, inprogress, waiting); } + + if (p->state == NICE_CHECK_FROZEN) + ++frozen; + else if (p->state == NICE_CHECK_IN_PROGRESS) + ++inprogress; + else if (p->state == NICE_CHECK_WAITING) + ++waiting; + else if (p->state == NICE_CHECK_SUCCEEDED) + ++succeeded; + + if (p->nominated) + ++nominated; + } + +#ifndef NDEBUG + { + static int tick_counter = 0; + if (++tick_counter % 20 == 0 || keep_timer_going != TRUE) + g_debug ("timer: %d frozen, %d in-progress, %d waiting, %d succeeded, %d nominated.", + frozen, inprogress, waiting, succeeded, nominated); + } #endif + + /* note: keep the timer going as long as there is work to be done */ + if (frozen || inprogress || waiting || + (succeeded && nominated == 0)) + keep_timer_going = TRUE; - /* note: keep the timer going as long as there is work to be done */ - if (frozen || inprogress || waiting) - keep_timer_going = TRUE; - } + /* step: nominate some candidate + * - no work left but still no nominated checks (possibly + * caused by a controlling-controlling role conflict) + */ + if (agent->controlling_mode && + (!frozen && !inprogress && !waiting && !nominated)) { + g_debug ("no nominated checks, resending checks to select one"); + for (i = agent->conncheck_list; i ; i = i->next) { + CandidateCheckPair *p = i->data; + /* XXX: should we pick highest priority one? probably yes. */ + if (p->state == NICE_CHECK_SUCCEEDED || + p->state == NICE_CHECK_DISCOVERED) { + g_debug ("restarting check %p as the nominated pair.", p); + p->nominated = TRUE; + priv_conn_check_initiate (agent, p); + keep_timer_going = TRUE; + } + } + } + + /* step: stop timer if no work left */ if (keep_timer_going != TRUE) { g_debug ("%s: stopping conncheck timer", G_STRFUNC); for (i = agent->streams; i; i = i->next) { Stream *stream = i->data; priv_update_check_list_state (agent, stream); } - conn_check_free (agent); + agent->conncheck_state = NICE_CHECKLIST_COMPLETED; + /* XXX: what to signal, is all processing now really done? */ + g_debug ("changing conncheck state to COMPLETED."); } return keep_timer_going; } /** + * Timer callback that handles initiating and managing connectivity + * checks (paced by the Ta timer). + * + * This function is designed for the g_timeout_add() interface. + * + * @return will return FALSE when no more pending timers. + */ +static gboolean priv_conn_keepalive_tick (gpointer pointer) +{ + NiceAgent *agent = pointer; + GSList *i; + int errors = 0; + + g_debug ("%s", G_STRFUNC); + + /* case 1: session established and media flowing + * (ref ICE sect 10 ID-16) */ + for (i = agent->streams; i; i = i->next) { + /* XXX: not multi-component ready */ + Stream *stream = i->data; + Component *component = stream->component; + if (component->selected_pair.local != NULL && + component->media_after_tick != TRUE) { + CandidatePair *p = &component->selected_pair; + struct sockaddr sockaddr; + int res; + + memset (&sockaddr, 0, sizeof (sockaddr)); + nice_address_copy_to_sockaddr (&p->remote->addr, &sockaddr); + + res = stun_bind_keepalive (p->local->sockptr->fileno, + &sockaddr, sizeof (sockaddr)); + g_debug ("stun_bind_keepalive for pair %p res %d.", p, res); + if (res < 0) + ++errors; + } + component->media_after_tick = FALSE; + } + + /* case 2: connectivity establishment ongoing + * (ref ICE sect 4.1.1.5 ID-15) */ + if (agent->conncheck_state == NICE_CHECKLIST_RUNNING) { + for (i = agent->conncheck_list; i ; i = i->next) { + CandidateCheckPair *p = i->data; + + if (p->traffic_after_tick != TRUE) { + g_debug ("resending STUN-CC to keep the candidate alive (pair %p).", p); + conn_check_send (agent, p); + } + p->traffic_after_tick = FALSE; + } + } + + if (errors) { + g_debug ("%s: stopping keepalive timer", G_STRFUNC); + return FALSE; + } + + return TRUE; +} + +/** * Initiates the next pending connectivity check. */ void conn_check_schedule_next (NiceAgent *agent) { gboolean c = priv_conn_check_unfreeze_next (agent->conncheck_list); + if (agent->discovery_unsched_items > 0) + g_debug ("WARN: starting conn checks before local candidate gathering is finished."); + if (c == TRUE) { /* step: call once imediately */ gboolean res = priv_conn_check_tick ((gpointer) agent); /* step: schedule timer if not running yet */ - /* XXX: make timeout Ta configurable */ if (agent->conncheck_timer_id == 0 && res != FALSE) agent->conncheck_timer_id = - g_timeout_add (NICE_AGENT_TIMER_TA_DEFAULT, priv_conn_check_tick, agent); + g_timeout_add (agent->timer_ta, priv_conn_check_tick, agent); + + /* step: also start the keepalive timer */ + if (agent->keepalive_timer_id == 0) + agent->keepalive_timer_id = + g_timeout_add (NICE_AGENT_TIMER_TR_DEFAULT, priv_conn_keepalive_tick, agent); + } } +/** + * Compares two connectivity check items. Checkpairs are sorted + * in descending priority order, with highest priority item at + * the start of the list. + */ +gint conn_check_compare (const CandidateCheckPair *a, const CandidateCheckPair *b) +{ + if (a->priority > b->priority) + return -1; + else if (a->priority < b->priority) + return 1; + return 0; +} + +static gboolean priv_add_new_check_pair (NiceAgent *agent, guint stream_id, Component *component, NiceCandidate *local, NiceCandidate *remote, NiceCheckState initial_state, gboolean use_candidate) +{ + gboolean result = FALSE; + + CandidateCheckPair *pair = g_slice_new0 (CandidateCheckPair); + if (pair) { + pair->agent = agent; + pair->stream_id = stream_id; + pair->component_id = component->id;; + pair->local = local; + pair->remote = remote; + g_snprintf (pair->foundation, NICE_CANDIDATE_PAIR_MAX_FOUNDATION, "%s:%s", local->foundation, remote->foundation); + + if (agent->controlling_mode == TRUE) + pair->priority = nice_candidate_pair_priority (local->priority, remote->priority); + else + pair->priority = nice_candidate_pair_priority (remote->priority, local->priority); + pair->state = NICE_CHECK_FROZEN; + pair->nominated = use_candidate; + + /* note: for the first added check */ + if (!agent->conncheck_list) + agent->conncheck_state = NICE_CHECKLIST_RUNNING; + + { + GSList *modified_list = + g_slist_insert_sorted (agent->conncheck_list, pair, (GCompareFunc)conn_check_compare); + if (modified_list) { + agent->conncheck_list = modified_list; + result = TRUE; + g_debug ("added a new conncheck item with foundation of '%s'.", pair->foundation); + } + else { /* memory alloc failed: list insert */ + conn_check_free_item (pair, NULL); + agent->conncheck_state = NICE_CHECKLIST_FAILED; + } + } + } + else { /* memory alloc failed: new pair */ + agent->conncheck_state = NICE_CHECKLIST_FAILED; + } + + return result; +} + /** * Forms new candidate pairs by matching the new remote candidate * 'remote_cand' with all existing local candidates of 'component'. @@ -255,58 +428,40 @@ void conn_check_schedule_next (NiceAgent *agent) * @param component pointer to the component * @param remote remote candidate to match with * - * @return non-zero on error + * @return number of checks added, negative on fatal errors */ int conn_check_add_for_candidate (NiceAgent *agent, guint stream_id, Component *component, NiceCandidate *remote) { GSList *i; - int res = 0; + int added = 0; for (i = component->local_candidates; i ; i = i->next) { NiceCandidate *local = i->data; - CandidateCheckPair *pair = g_slice_new0 (CandidateCheckPair); - if (pair) { - - /* XXX: as per -15 5.7.3, filter pairs where local candidate is - srvrflx and base matches a local candidate for which there - already is a check pair - */ - pair->agent = agent; - pair->stream_id = stream_id; - pair->component_id = component->id;; - pair->local = local; /* XXX: hmm, do we need reference counting, - or we just make sure all connchecks are - destroyed before any components of a stream...? */ - pair->remote = remote; - pair->foundation = g_strdup_printf ("%s%s", local->foundation, remote->foundation); - if (!pair->foundation) { - g_slice_free (CandidateCheckPair, pair); - res = -1; - break; - } - - pair->priority = nice_candidate_pair_priority (local->priority, remote->priority); - pair->state = NICE_CHECK_FROZEN; + /* note: match pairs only if transport and address family are the same */ + if (local->transport == remote->transport && + local->addr.type == remote->addr.type) { - if (!agent->conncheck_list) - agent->conncheck_state = NICE_CHECKLIST_RUNNING; - - agent->conncheck_list = g_slist_append (agent->conncheck_list, pair); + gboolean result; - if (!agent->conncheck_list) - agent->conncheck_state = NICE_CHECKLIST_FAILED; + /* note: do not create pairs where local candidate is + * a srv-reflexive (ICE 5.7.3 ID-15) */ + if (local->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE) + continue; - g_debug ("added a new conncheck item with foundation of '%s'.", pair->foundation); - } - else { - res = -1; - break; + result = priv_add_new_check_pair (agent, stream_id, component, local, remote, NICE_CHECK_FROZEN, FALSE); + if (result) { + ++added; + } + else { + added = -1; + break; + } } } - return res; + return added; } /** @@ -317,9 +472,6 @@ void conn_check_free_item (gpointer data, gpointer user_data) { CandidateCheckPair *pair = data; g_assert (user_data == NULL); - if (pair->foundation) - g_free (pair->foundation), - pair->foundation = NULL; if (pair->stun_ctx) stun_bind_cancel (pair->stun_ctx), pair->stun_ctx = NULL; @@ -335,12 +487,12 @@ void conn_check_free (NiceAgent *agent) g_slist_foreach (agent->conncheck_list, conn_check_free_item, NULL); g_slist_free (agent->conncheck_list), agent->conncheck_list = NULL; - if (agent->conncheck_timer_id) { - g_source_remove (agent->conncheck_timer_id), - agent->conncheck_timer_id = 0; - } agent->conncheck_state = NICE_CHECKLIST_NOT_STARTED; } + if (agent->conncheck_timer_id) { + g_source_remove (agent->conncheck_timer_id), + agent->conncheck_timer_id = 0; + } } /** @@ -373,8 +525,10 @@ gboolean conn_check_prune_stream (NiceAgent *agent, guint stream_id) i = i->next; } - if (!agent->conncheck_list) + if (!agent->conncheck_list) { agent->conncheck_state = NICE_CHECKLIST_NOT_STARTED; + conn_check_free (agent); + } /* return FALSE if there was a memory allocation failure */ if (agent->conncheck_list == NULL && i != NULL) @@ -385,24 +539,28 @@ gboolean conn_check_prune_stream (NiceAgent *agent, guint stream_id) /** - * Returns a username string for use in an outbound connectivity - * check. The caller is responsible for freeing the returned - * string. + * Fills 'dest' with a username string for use in an outbound connectivity + * checks. No more than 'dest_len' characters (including terminating + * NULL) is ever written to the 'dest'. */ -static gchar *priv_create_check_username (NiceAgent *agent, CandidateCheckPair *pair) +static gboolean priv_create_check_username (NiceAgent *agent, CandidateCheckPair *pair, gchar *dest, guint dest_len) { Stream *stream; if (pair && pair->remote && pair->remote->username && - pair->local && pair->local->username) - return g_strconcat (pair->remote->username, ":", pair->local->username, NULL); + pair->local && pair->local->username) { + g_snprintf (dest, dest_len, "%s:%s", pair->remote->username, pair->local->username); + return TRUE; + } stream = agent_find_stream (agent, pair->stream_id); - if (stream) - return g_strconcat (stream->remote_ufrag, ":", stream->local_ufrag, NULL); + if (stream) { + g_snprintf (dest, dest_len, "%s:%s", stream->remote_ufrag, stream->local_ufrag); + return TRUE; + } - return NULL; + return FALSE; } /** @@ -426,6 +584,8 @@ static const gchar *priv_create_check_password (NiceAgent *agent, CandidateCheck /** * Sends a connectivity check over candidate pair 'pair'. + * + * @return zero on success, non-zero on error */ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) { @@ -443,47 +603,60 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) NICE_CANDIDATE_TYPE_PREF_PEER_REFLEXIVE, 1, pair->local->component_id); - /* XXX: memory allocs: */ - gchar *username = priv_create_check_username (agent, pair); + + gboolean username_filled = + priv_create_check_username (agent, pair, agent->ufragtmp, NICE_STREAM_MAX_UNAME); const gchar *password = priv_create_check_password (agent, pair); + bool controlling = agent->controlling_mode; /* XXX: add API to support different nomination modes: */ bool cand_use = controlling; - uint64_t tie; struct sockaddr sockaddr; unsigned int timeout; memset (&sockaddr, 0, sizeof (sockaddr)); - nice_rng_generate_bytes (agent->rng, 4, (gchar*)&tie); nice_address_copy_to_sockaddr (&pair->remote->addr, &sockaddr); - g_debug ("sending STUN conncheck, port:%u, socket:%u, tie:%llu, username:'%s', password:'%s', priority:%u.", - ntohs(((struct sockaddr_in*)(&sockaddr))->sin_port), - pair->local->sockptr->fileno, - (unsigned long long)tie, - username, password, priority); +#ifndef NDEBUG + { + gchar tmpbuf[INET6_ADDRSTRLEN]; + nice_address_to_string (&pair->remote->addr, tmpbuf); + g_debug ("STUN-CC REQ to '%s:%u', socket=%u, pair=%s, tie=%llu, username='%s', password='%s', priority=%u.", + tmpbuf, + ntohs(((struct sockaddr_in*)(&sockaddr))->sin_port), + pair->local->sockptr->fileno, + pair->foundation, + (unsigned long long)agent->tie_breaker, + agent->ufragtmp, password, priority); + + } +#endif if (cand_use) - pair->nominated = TRUE; + pair->nominated = controlling; - stun_conncheck_start (&pair->stun_ctx, pair->local->sockptr->fileno, - &sockaddr, sizeof (sockaddr), - username, password, - cand_use, controlling, priority, - tie); + if (username_filled) { - timeout = stun_bind_timeout (pair->stun_ctx); - /* note: convert from milli to microseconds for g_time_val_add() */ - g_get_current_time (&pair->next_tick); - g_time_val_add (&pair->next_tick, timeout * 10); + if (pair->stun_ctx) + stun_bind_cancel (pair->stun_ctx); - g_debug ("set timeout for conncheck %p to %u.", pair, timeout); + stun_conncheck_start (&pair->stun_ctx, pair->local->sockptr->fileno, + &sockaddr, sizeof (sockaddr), + agent->ufragtmp, password, + cand_use, controlling, priority, + agent->tie_breaker); - if (username) - g_free (username); - + timeout = stun_bind_timeout (pair->stun_ctx); + /* note: convert from milli to microseconds for g_time_val_add() */ + g_get_current_time (&pair->next_tick); + g_time_val_add (&pair->next_tick, timeout * 10); + g_debug ("set timeout for conncheck %p to %u.", pair, timeout); + + pair->traffic_after_tick = TRUE; /* for keepalive timer */ + } + return 0; } @@ -500,41 +673,35 @@ static void priv_update_check_list_state (NiceAgent *agent, Stream *stream) /* note: iterate the conncheck list for each component separately */ for (c = 0; c < stream->n_components; c++) { - guint not_failed = 0; + guint not_failed = 0, checks = 0; for (i = agent->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; if (p->stream_id == stream->id && p->component_id == (c + 1)) { + ++checks; + if (p->state != NICE_CHECK_FAILED) ++not_failed; - if (p->state == NICE_CHECK_SUCCEEDED && + if ((p->state == NICE_CHECK_SUCCEEDED || + p->state == NICE_CHECK_DISCOVERED) && p->nominated == TRUE) break; } } /* note: all checks have failed */ - if (!not_failed) + if (checks > 0 && not_failed == 0) agent_signal_component_state_change (agent, stream->id, (c + 1), /* component-id */ NICE_COMPONENT_STATE_FAILED); - /* note: no pair was ready&nominated */ + /* note: no pair was ready/discovered and nominated */ if (i == NULL) ++completed; } - - if (completed == stream->n_components) { - /* note: all components completed */ - /* XXX: not really true as there can be checks for multiple - * streams in the conncheck list... :o */ - agent->conncheck_state = NICE_CHECKLIST_COMPLETED; - /* XXX: what to signal, is all processing now really done? */ - g_debug ("changing conncheck state to COMPLETED)"); - } } /** @@ -554,7 +721,8 @@ static void priv_update_check_list_state_for_component (NiceAgent *agent, Stream for (i = agent->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; if (p->component_id == component->id) { - if (p->state == NICE_CHECK_SUCCEEDED) { + if (p->state == NICE_CHECK_SUCCEEDED || + p->state == NICE_CHECK_DISCOVERED) { ++succeeded; if (p->nominated == TRUE) { ++nominated; @@ -613,6 +781,78 @@ static gboolean priv_update_selected_pair (NiceAgent *agent, Component *componen } /** + * Schedules a triggered check after a succesfully inbound + * connectivity check. Implements ICE sect 7.2.1.4 (ID-16). + * + * @param agent self pointer + * @param component the check is related to + * @param local_socket socket from which the inbound check was received + * @param remote_cand remote candidate from which the inbound check was sent + * @param use_candidate whether the original check had USE-CANDIDATE attribute set + */ +static gboolean priv_schedule_triggered_check (NiceAgent *agent, Stream *stream, Component *component, NiceUDPSocket *local_socket, NiceCandidate *remote_cand, gboolean use_candidate) +{ + GSList *i; + + for (i = agent->conncheck_list; i ; i = i->next) { + CandidateCheckPair *p = i->data; + if (p->component_id == component->id && + p->remote == remote_cand && + p->local->sockptr == local_socket) { + + g_debug ("Found a matching pair %p for triggered check.", p); + + if (p->state == NICE_CHECK_WAITING || + p->state == NICE_CHECK_FROZEN) + priv_conn_check_initiate (agent, p); + + if (p->state == NICE_CHECK_IN_PROGRESS) { + /* XXX: according to ICE 7.2.1.4 (ID-16), we should cancel + * the existing one, and send a new one...? :P */ + g_debug ("Skipping triggered check, already in progress.."); + } + else if (p->state == NICE_CHECK_SUCCEEDED || + p->state == NICE_CHECK_DISCOVERED) { + g_debug ("Skipping triggered check, already completed.."); + /* note: this is a bit unsure corner-case -- let's do the + same state update as for processing responses to our own checks */ + priv_update_check_list_state_for_component (agent, stream, component); + + /* note: to take care of the controlling-controlling case in + * aggressive nomination mode, send a new triggered + * check to nominate the pair */ + if (agent->controlling_mode) + priv_conn_check_initiate (agent, p); + } + + /* note: the spec says the we SHOULD retransmit in-progress + * checks immediately, but we won't do that now */ + + return TRUE; + } + } + + { + gboolean result = FALSE; + NiceCandidate *local = NULL; + + for (i = component->local_candidates; i ; i = i->next) { + local = i->data; + if (local->sockptr == local_socket) + break; + } + if (local) { + g_debug ("Adding a triggered check to conn.check list."); + result = priv_add_new_check_pair (agent, stream->id, component, local, remote_cand, NICE_CHECK_WAITING, use_candidate); + } + else + g_debug ("Didn't find a matching pair for triggered check (remote-cand=%p).", remote_cand); + } + + return FALSE; +} + +/** * The remote party has signalled that the candidate pair * described by 'component' and 'remotecand' is nominated * for use. @@ -626,394 +866,397 @@ static void priv_mark_pair_nominated (NiceAgent *agent, Component *component, Ni /* step: search for at least one nominated pair */ for (i = agent->conncheck_list; i; i = i->next) { CandidateCheckPair *pair = i->data; - /* XXX: hmm, how figure out to which local candidate the + /* XXX: hmm, how to figure out to which local candidate the * check was sent to? let's mark all matching pairs * as nominated instead */ if (pair->remote == remotecand) { g_debug ("marking pair %p (%s) as nominated", pair, pair->foundation); pair->nominated = TRUE; - if (pair->state == NICE_CHECK_SUCCEEDED) + if (pair->state == NICE_CHECK_SUCCEEDED || + pair->state == NICE_CHECK_DISCOVERED) priv_update_selected_pair (agent, component, pair); } } - } -gboolean conn_check_handle_inbound_stun (NiceAgent *agent, Stream *stream, Component *component, const NiceAddress *from, gchar *buf, guint len) +/** + * Sends a reply to an succesfully received STUN connectivity + * check request. Implements parts of the ICE spec section 7.2 (STUN + * Server Procedures). + */ +static void priv_reply_to_conn_check (NiceAgent *agent, Stream *stream, Component *component, NiceCandidate *cand, NiceUDPSocket *udp_socket, size_t rbuf_len, uint8_t *rbuf, gboolean use_candidate) { - struct sockaddr sockaddr; - uint8_t rbuf[MAX_STUN_DATAGRAM_PAYLOAD]; - ssize_t res; - size_t rbuf_len = sizeof (rbuf); - bool control = agent->controlling_mode; - uint64_t tie = -1; /* XXX: fix properly */ - - nice_address_copy_to_sockaddr (from, &sockaddr); - - /* note: contents of 'buf' already validated, so it is - * a valid and full received STUN message */ +#ifndef NDEBUG + { + gchar tmpbuf[INET6_ADDRSTRLEN]; + nice_address_to_string (&cand->addr, tmpbuf); + g_debug ("STUN-CC RESP to '%s:%u', socket=%u, len=%u, cand=%p, use-cand=%d.", + tmpbuf, + cand->addr.port, + udp_socket->fileno, + rbuf_len, + cand, + (int)use_candidate); + } +#endif - /* note: ICE ID-15, 7.2 */ - res = stun_conncheck_reply (rbuf, &rbuf_len, (const uint8_t*)buf, &sockaddr, sizeof (sockaddr), - stream->local_password, &control, tie); - if (res == 0) { - /* case 1: valid incoming request, send a reply */ - GSList *i; - NiceUDPSocket *local_sock = NULL; - bool use_candidate = - stun_conncheck_use_candidate ((const uint8_t*)buf); + nice_udp_socket_send (udp_socket, &cand->addr, rbuf_len, (const gchar*)rbuf); + + if (use_candidate) + priv_mark_pair_nominated (agent, component, cand); - if (stream->initial_binding_request_received != TRUE) - agent_signal_initial_binding_request_received (agent, stream); + /* note: upon succesful check, make the reserve check immediately */ + priv_schedule_triggered_check (agent, stream, component, udp_socket, cand, use_candidate); +} - if (control != agent->controlling_mode) { - g_debug ("Conflict in controller selection, switching to mode %d.", control); - agent->controlling_mode = control; +/** + * Adds a new pair, discovered from an incoming STUN response, to + * the connectivity check list. + * + * @return created pair, or NULL on fatal (memory allocation) errors + */ +static CandidateCheckPair *priv_add_peer_reflexive_pair (NiceAgent *agent, guint stream_id, guint component_id, NiceCandidate *local_cand, CandidateCheckPair *parent_pair) +{ + CandidateCheckPair *pair = g_slice_new0 (CandidateCheckPair); + if (pair) { + GSList *modified_list = g_slist_append (agent->conncheck_list, pair); + if (modified_list) { + agent->conncheck_list = modified_list; + pair->agent = agent; + pair->stream_id = stream_id; + pair->component_id = component_id;; + pair->local = local_cand; + pair->remote = parent_pair->remote; + pair->state = NICE_CHECK_DISCOVERED; + g_snprintf (pair->foundation, NICE_CANDIDATE_PAIR_MAX_FOUNDATION, "%s:%s", local_cand->foundation, parent_pair->remote->foundation); + if (agent->controlling_mode == TRUE) + pair->priority = nice_candidate_pair_priority (local_cand->priority, parent_pair->remote->priority); + else + pair->priority = nice_candidate_pair_priority (parent_pair->remote->priority, local_cand->priority); + pair->nominated = FALSE; + g_debug ("added a new peer-discovered pair with foundation of '%s'.", pair->foundation); + return pair; } + } - /* XXX/hack: until the socket refactoring is done, use the below hack */ - for (i = component->local_candidates; i; i = i->next) { - NiceCandidate *cand = i->data; - if (cand->sockptr->fileno > 0) - local_sock = cand->sockptr; - } + return NULL; +} - for (i = component->remote_candidates; local_sock && i; i = i->next) { - NiceCandidate *cand = i->data; - if (nice_address_equal (from, &cand->addr)) { - g_debug ("Sending a conncheck reply (fileno=%u, addr=%p, res=%u, buf=%p).", - local_sock->fileno, &cand->addr, rbuf_len, rbuf); +/** + * Tries to match STUN reply in 'buf' to an existing STUN connectivity + * check transaction. If found, the reply is processed. Implements + * section 7.1.2 (Processing the Response) of ICE spec (ID-16). + * + * @return TRUE if a matching transaction is found + */ +static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, Stream *stream, Component *component, NiceUDPSocket *sockptr, gchar *buf, guint len) +{ + struct sockaddr sockaddr; + socklen_t socklen = sizeof (sockaddr); + GSList *i; + ssize_t res; + gboolean trans_found = FALSE; - nice_udp_socket_send (local_sock, &cand->addr, rbuf_len, (const gchar*)rbuf); + for (i = agent->conncheck_list; i && trans_found != TRUE; i = i->next) { + CandidateCheckPair *p = i->data; + if (p->stun_ctx) { + res = stun_bind_process (p->stun_ctx, buf, len, &sockaddr, &socklen); + g_debug ("stun_bind_process/conncheck for %p res %d (controlling=%d).", p, res, agent->controlling_mode); + if (res == 0) { + /* case: found a matching connectivity check request */ + + CandidateCheckPair *ok_pair = p; + + gboolean local_cand_matches = FALSE; + g_debug ("conncheck %p MATCHED.", p); + p->stun_ctx = NULL; + + /* note: CONNECTED but not yet READY, see docs */ + + /* step: handle the possible case of a peer-reflexive + * candidate where the mapped-address in response does + * not match any local candidate, see 7.1.2.2.1 in ICE ID-16) */ + { + NiceAddress mapped; + GSList *j; + nice_address_set_from_sockaddr (&mapped, &sockaddr); + + for (j = component->local_candidates; j; j = j->next) { + NiceCandidate *cand = j->data; + if (nice_address_equal (&mapped, &cand->addr)) { + local_cand_matches = TRUE; + break; + } + } - if (use_candidate) - priv_mark_pair_nominated (agent, component, cand); + if (local_cand_matches == TRUE) { + /* note: this is same as "adding to VALID LIST" in the spec + text */ + p->state = NICE_CHECK_SUCCEEDED; + g_debug ("conncheck %p SUCCEEDED.", p); + } + else { + NiceCandidate *cand = + discovery_add_peer_reflexive_candidate ( + agent, + stream->id, + component->id, + &mapped, + sockptr); + CandidateCheckPair *new_pair; - /* XXX: perform a triggered check */ + p->state = NICE_CHECK_FAILED; - break; - } - } + /* step: add a new discovered pair (see ICE 7.1.2.2.2 ID-16) */ + new_pair = priv_add_peer_reflexive_pair (agent, stream->id, component->id, cand, p); + ok_pair = new_pair; - /* XXX: add support for discovering peer-reflexive candidates */ - if (i == NULL) - g_debug ("No matching remote candidate for incoming STUN conncheck."); + g_debug ("conncheck %p FAILED, %p DISCOVERED.", p, new_pair); + } + } - } - else if (res == EINVAL) { - /* case 2: not a valid, new request -- continue processing */ - GSList *i; - int res; - socklen_t socklen = sizeof (sockaddr); - gboolean trans_found = FALSE; + /* step: notify the client of a new component state (must be done + * before the possible check list state update step */ + agent_signal_component_state_change (agent, + stream->id, + component->id, + NICE_COMPONENT_STATE_CONNECTED); - g_debug ("Not a STUN connectivity check request -- might be a reply..."); - - /* note: ICE ID-15, 7.1.2 */ - /* step: let's try to match the response to an existing check context */ - for (i = agent->conncheck_list; i && trans_found != TRUE; i = i->next) { - CandidateCheckPair *p = i->data; - if (p->stun_ctx) { - res = stun_bind_process (p->stun_ctx, buf, len, &sockaddr, &socklen); - g_debug ("stun_bind_process/conncheck for %p res %d.", p, res); - if (res == 0) { - /* case: succesful connectivity check */ - g_debug ("conncheck %p SUCCEED.", p); - p->state = NICE_CHECK_SUCCEEDED; - p->stun_ctx = NULL; - /* note: CONNECTED but not yet READY, see docs */ - agent_signal_component_state_change (agent, - stream->id, - component->id, - NICE_COMPONENT_STATE_CONNECTED); - priv_update_check_list_state_for_component (agent, stream, component); - if (p->nominated == TRUE) { - priv_update_selected_pair (agent, component, p); - } - trans_found = TRUE; - } - else if (res != EAGAIN) { - /* case: STUN error, the check STUN context was freed */ - g_debug ("conncheck %p FAILED.", p); - p->stun_ctx = NULL; - trans_found = TRUE; - } - else - /* case: invalid/incomplete message */ - g_assert (res == EAGAIN); - } - } + /* step: update pair states (ICE 7.1.2.2.3, ID-16) */ + priv_update_check_list_state_for_component (agent, stream, component); + + /* step: updating nominated flag (ICE 7.1.2.2.4, ID-15 */ + if (ok_pair->nominated == TRUE) + priv_update_selected_pair (agent, component, ok_pair); - /* step: let's try to match the response to an existing discovery */ - for (i = agent->discovery_list; i && trans_found != TRUE; i = i->next) { - CandidateDiscovery *d = i->data; - res = stun_bind_process (d->stun_ctx, buf, len, &sockaddr, &socklen); - g_debug ("stun_bind_process/disc for %p res %d.", d, res); - if (res == 0) { - /* case: succesful binding discovery, create a new local candidate */ - NiceAddress niceaddr; - struct sockaddr_in *mapped = (struct sockaddr_in *)&sockaddr; - niceaddr.type = NICE_ADDRESS_TYPE_IPV4; - niceaddr.addr_ipv4 = ntohl(mapped->sin_addr.s_addr); - niceaddr.port = ntohs(mapped->sin_port); - discovery_add_server_reflexive_candidate ( - d->agent, - d->stream->id, - d->component->id, - &niceaddr, - d->nicesock); - d->stun_ctx = NULL; - d->done = TRUE; trans_found = TRUE; } else if (res != EAGAIN) { /* case: STUN error, the check STUN context was freed */ - d->stun_ctx = NULL; + g_debug ("conncheck %p FAILED.", p); + p->stun_ctx = NULL; trans_found = TRUE; } + else { + g_assert (res == EAGAIN); + + /* XXX: stun won't restart automatically, so cancel and + * restart, should do this only for 487 errors (role conflict) */ + g_debug ("cancelling and restarting check for pair %p", p); + + stun_bind_cancel (p->stun_ctx), + p->stun_ctx = NULL; + p->state = NICE_CHECK_WAITING; + } } } - else { - g_debug ("Invalid STUN connectivity check request. Ignoring... %s", strerror(errno)); - } - - return TRUE; + + return trans_found; } -/* ----------------------------------------------------------------- - * Code using the old STUN API - * ----------------------------------------------------------------- */ - -static void -_handle_stun_binding_request ( - NiceAgent *agent, - Stream *stream, - Component *component, - NiceCandidate *local, - NiceAddress from, - StunMessage *msg) +/** + * Tries to match STUN reply in 'buf' to an existing STUN discovery + * transaction. If found, a reply is sent. + * + * @return TRUE if a matching transaction is found + */ +static gboolean priv_map_reply_to_discovery_request (NiceAgent *agent, gchar *buf, guint len) { + struct sockaddr sockaddr; + socklen_t socklen = sizeof (sockaddr); GSList *i; - StunAttribute *attr; - gchar *username = NULL; - NiceCandidate *remote = NULL; - - /* msg should have either: - * - * Jingle P2P: - * username = local candidate username + remote candidate username - * ICE: - * username = local candidate username + ":" + remote candidate username - * password = local candidate pwd - * priority = priority to use if a new candidate is generated - * - * Note that: - * - * - "local"/"remote" are from the perspective of the receiving side - * - the remote candidate username is not necessarily unique; Jingle seems - * to always generate a unique username/password for each candidate, but - * ICE makes no guarantees - * - * There are three cases we need to deal with: - * - * - valid username with a known address - * --> send response - * - valid username with an unknown address - * --> send response - * --> later: create new remote candidate - * - invalid username - * --> send error - */ + ssize_t res; + gboolean trans_found = FALSE; + + for (i = agent->discovery_list; i && trans_found != TRUE; i = i->next) { + CandidateDiscovery *d = i->data; + res = stun_bind_process (d->stun_ctx, buf, len, &sockaddr, &socklen); + g_debug ("stun_bind_process/disc for %p res %d.", d, res); + if (res == 0) { + /* case: succesful binding discovery, create a new local candidate */ + NiceAddress niceaddr; + struct sockaddr_in *mapped = (struct sockaddr_in *)&sockaddr; + niceaddr.type = NICE_ADDRESS_TYPE_IPV4; + niceaddr.addr.addr_ipv4 = ntohl(mapped->sin_addr.s_addr); + niceaddr.port = ntohs(mapped->sin_port); + discovery_add_server_reflexive_candidate ( + d->agent, + d->stream->id, + d->component->id, + &niceaddr, + d->nicesock); + + d->stun_ctx = NULL; + d->done = TRUE; + trans_found = TRUE; + } + else if (res != EAGAIN) { + /* case: STUN error, the check STUN context was freed */ + d->stun_ctx = NULL; + d->done = TRUE; + trans_found = TRUE; + } + else { + g_assert (res == EAGAIN); + } + } - attr = stun_message_find_attribute (msg, STUN_ATTRIBUTE_USERNAME); + return trans_found; +} - if (attr == NULL) - /* no username attribute found */ - goto ERROR; +static gboolean priv_verify_inbound_username (NiceAgent *agent, Stream *stream, const char *uname) +{ + const char *colon; - username = attr->username; + if (uname == NULL) + return FALSE; - /* validate username */ - /* XXX-old_stun_code: Should first try and find a remote candidate with a matching - * transport address, and fall back to matching on username only after that. - * That way, we know to always generate a new remote candidate if the - * transport address didn't match. - */ + colon = strchr (uname, ':'); + if (colon == NULL) + return FALSE; - for (i = component->remote_candidates; i; i = i->next) - { - guint len; + if (strncmp (uname, stream->local_ufrag, colon - uname) == 0) + return TRUE; - remote = i->data; + return FALSE; +} -#if 0 - g_debug ("uname check: %s :: %s -- %s", username, local->username, - remote->username); -#endif +/** + * Recalculates priorities of all candidate pairs. This + * is required after a conflict in ICE roles. + */ +static void priv_recalculate_pair_priorities (NiceAgent *agent) +{ + GSList *i; - if (!g_str_has_prefix (username, local->username)) - continue; + for (i = agent->conncheck_list; i; i = i->next) { + CandidateCheckPair *p = i->data; + if (agent->controlling_mode == TRUE) + p->priority = nice_candidate_pair_priority (p->local->priority, p->remote->priority); + else + p->priority = nice_candidate_pair_priority (p->remote->priority, p->local->priority); + } +} - len = strlen (local->username); +/** + * Processing an incoming STUN message. + * + * @param agent self pointer + * @param stream stream the packet is related to + * @param component component the packet is related to + * @param udp_socket UDP socket from which the packet was received + * @param from address of the sender + * @param buf message contents + * @param buf message length + * + * @pre contents of 'buf' is a STUN message + * + * @return XXX (what FALSE means exactly?) + */ +gboolean conn_check_handle_inbound_stun (NiceAgent *agent, Stream *stream, Component *component, NiceUDPSocket *udp_socket, const NiceAddress *from, gchar *buf, guint len) +{ + struct sockaddr sockaddr; + uint8_t rbuf[MAX_STUN_DATAGRAM_PAYLOAD]; + ssize_t res; + size_t rbuf_len = sizeof (rbuf); + bool control = agent->controlling_mode; - if (0 != strcmp (username + len, remote->username)) - continue; + nice_address_copy_to_sockaddr (from, &sockaddr); -#if 0 - /* usernames match; check address */ + /* note: contents of 'buf' already validated, so it is + * a valid and full received STUN message */ - if (rtmp->addr.addr_ipv4 == ntohl (from.sin_addr.s_addr) && - rtmp->port == ntohs (from.sin_port)) - { - /* this is a candidate we know about, just send a reply */ - /* is candidate pair active now? */ - remote = rtmp; - } -#endif + /* note: ICE ID-16, 7.2 */ - /* send response */ - goto RESPOND; + res = stun_conncheck_reply (rbuf, &rbuf_len, (const uint8_t*)buf, &sockaddr, sizeof (sockaddr), + stream->local_password, &control, agent->tie_breaker); + if (res == EACCES) { + /* role conflict, change mode and regenarate reply */ + if (control != agent->controlling_mode) { + g_debug ("Conflict in controller selection, switching to mode %d.", control); + agent->controlling_mode = control; + /* the pair priorities depend on the roles, so recalculation + * is needed */ + priv_recalculate_pair_priorities (agent); } + } - /* username is not valid */ - goto ERROR; + if (res == 0 || res == EACCES) { + /* case 1: valid incoming request, send a reply */ + + GSList *i; + bool use_candidate = + stun_conncheck_use_candidate ((const uint8_t*)buf); -RESPOND: + /* XXX: uses stun-msg.h, remove later */ + bool error_response = + (stun_get_class ((const uint8_t*)rbuf) == STUN_ERROR); + g_debug ("error in request %d.", error_response); -#ifdef DEBUG - { - gchar ip[NICE_ADDRESS_STRING_LEN]; + if (agent->controlling_mode) + use_candidate = TRUE; - nice_address_to_string (&remote->addr, ip); - g_debug ("s%d:%d: got valid connectivity check for candidate %d (%s:%d)", - stream->id, component->id, remote->id, ip, remote->addr.port); + /* XXX: verify the USERNAME with stun_connecheck_username() */ + if (stun_conncheck_username ((const uint8_t*)buf, agent->ufragtmp, NICE_STREAM_MAX_UNAME) == NULL) { + g_debug ("No USERNAME attribute in incoming STUN request, ignoring."); + return FALSE; } -#endif - - /* update candidate/peer affinity */ - /* Note that @from might be different to @remote->addr; for ICE, this - * (always?) creates a new peer-reflexive remote candidate (ยง7.2). - */ - /* XXX-old_stun_code: test case where @from != @remote->addr. */ - - component->active_candidate = local; - component->peer_addr = from; - - /* send STUN response */ - - { - StunMessage *response; - guint len; - gchar *packed; - - response = stun_message_new (STUN_MESSAGE_BINDING_RESPONSE, - msg->transaction_id, 2); - response->attributes[0] = stun_attribute_mapped_address_new ( - from.addr_ipv4, from.port); - response->attributes[1] = stun_attribute_username_new (username); - len = stun_message_pack (response, &packed); - nice_udp_socket_send (local->sockptr, &from, len, packed); - - g_free (packed); - stun_message_free (response); + + if (priv_verify_inbound_username (agent, stream, agent->ufragtmp) != TRUE) { + g_debug ("USERNAME does not match local streams, ignoring."); + return FALSE; } - /* send reciprocal ("triggered") connectivity check */ - /* XXX-old_stun_code: possibly we shouldn't do this if we're being an ICE Lite agent */ - - { - StunMessage *extra; - gchar *username; - guint len; - gchar *packed; - - extra = stun_message_new (STUN_MESSAGE_BINDING_REQUEST, - NULL, 1); - - username = g_strconcat (remote->username, local->username, NULL); - extra->attributes[0] = stun_attribute_username_new (username); - g_free (username); - - nice_rng_generate_bytes (agent->rng, 16, extra->transaction_id); - - len = stun_message_pack (extra, &packed); - nice_udp_socket_send (local->sockptr, &from, len, packed); - g_free (packed); + if (stream->initial_binding_request_received != TRUE) + agent_signal_initial_binding_request_received (agent, stream); - stun_message_free (extra); + for (i = component->remote_candidates; i; i = i->next) { + NiceCandidate *cand = i->data; + if (nice_address_equal (from, &cand->addr)) { + priv_reply_to_conn_check (agent, stream, component, cand, udp_socket, rbuf_len, rbuf, use_candidate); + break; + } } - /* emit component-state-changed(connected) */ - /* XXX-old_stun_code: probably better do this when we get the binding response */ - agent_signal_component_state_change (agent, - stream->id, - component->id, - NICE_COMPONENT_STATE_CONNECTED); - - return; - -ERROR: - -#ifdef DEBUG - { - gchar ip[NICE_ADDRESS_STRING_LEN]; - - nice_address_to_string (&remote->addr, ip); - g_debug ( - "s%d:%d: got invalid connectivity check for candidate %d (%s:%d)", - stream->id, component->id, remote->id, ip, remote->addr.port); + if (i == NULL) { + NiceCandidate *candidate; + g_debug ("No matching remote candidate for incoming check -> peer-reflexive candidate."); + candidate = discovery_learn_remote_peer_reflexive_candidate ( + agent, stream, component, stun_conncheck_priority ((const uint8_t*)buf), from, udp_socket); + if (candidate) + priv_reply_to_conn_check (agent, stream, component, candidate, udp_socket, rbuf_len, rbuf, use_candidate); } -#endif - - /* XXX-old_stun_code: add ERROR-CODE parameter */ + } + else if (res == EINVAL) { + /* case 2: not a new request, might be a reply... */ - { - StunMessage *response; - guint len; - gchar *packed; + gboolean trans_found = FALSE; - response = stun_message_new (STUN_MESSAGE_BINDING_ERROR_RESPONSE, - msg->transaction_id, 0); - len = stun_message_pack (response, &packed); - nice_udp_socket_send (local->sockptr, &from, len, packed); + /* XXX: uses stun-msg.h, remove later */ + bool error_response = + (stun_get_class ((const uint8_t*)buf) == STUN_ERROR); + g_debug ("error in response %d", error_response); - g_free (packed); - stun_message_free (response); - } + g_debug ("Not a STUN connectivity check request, might be a reply or keepalive..."); - /* XXX-old_stun_code: we could be clever and keep around STUN packets that we couldn't - * validate, then re-examine them when we get new remote candidates -- would - * this fix some timing problems (i.e. TCP being slower than UDP) - */ - /* XXX-old_stun_code: if the peer is the controlling agent, it may include a USE-CANDIDATE - * attribute in the binding request - */ -} + /* note: ICE ID-15, 7.1.2 */ -void conn_check_handle_inbound_stun_old ( - NiceAgent *agent, - Stream *stream, - Component *component, - NiceCandidate *local, - NiceAddress from, - StunMessage *msg) -{ - switch (msg->type) - { - case STUN_MESSAGE_BINDING_REQUEST: - _handle_stun_binding_request (agent, stream, component, local, from, - msg); - break; - case STUN_MESSAGE_BINDING_RESPONSE: - /* XXX-old_stun_code: check it matches a request we sent */ - break; - default: - /* a message type we don't know how to handle */ - /* XXX-old_stun_code: send error response */ - break; - } -} + /* step: let's try to match the response to an existing check context */ + if (trans_found != TRUE) + trans_found = + priv_map_reply_to_conn_check_request (agent, stream, component, udp_socket, buf, len); + /* step: let's try to match the response to an existing discovery */ + if (trans_found != TRUE) + trans_found = + priv_map_reply_to_discovery_request (agent, buf, len); + } + else { + g_debug ("Invalid STUN connectivity check request. Ignoring... %s", strerror(errno)); + return FALSE; + } + return TRUE; +} diff --git a/agent/conncheck.h b/agent/conncheck.h index 7bfe4d3..a41e535 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -44,7 +44,9 @@ #include "agent.h" #include "stream.h" #include "stun.h" /* XXX: using the old STUN API, to be removed */ -#include "stun/conncheck.h" /* note: the new STUN API */ +#include "stun-ice.h" /* note: the new STUN API */ + +#define NICE_CANDIDATE_PAIR_MAX_FOUNDATION NICE_CANDIDATE_MAX_FOUNDATION*2 typedef enum { @@ -53,7 +55,8 @@ typedef enum NICE_CHECK_SUCCEEDED, /**< conn. succesfully checked */ NICE_CHECK_FAILED, /**< no connectivity, retransmissions ceased */ NICE_CHECK_FROZEN, /**< waiting to be scheduled to WAITING */ - NICE_CHECK_CANCELLED /**< check cancelled */ + NICE_CHECK_CANCELLED, /**< check cancelled */ + NICE_CHECK_DISCOVERED /**< a valid candidate pair not on check list */ } NiceCheckState; typedef enum @@ -73,11 +76,12 @@ struct _CandidateCheckPair guint component_id; NiceCandidate *local; NiceCandidate *remote; - gchar *foundation; + gchar foundation[NICE_CANDIDATE_PAIR_MAX_FOUNDATION]; NiceCheckState state; gboolean nominated; guint64 priority; GTimeVal next_tick; /* next tick timestamp */ + gboolean traffic_after_tick; stun_bind_t *stun_ctx; }; @@ -87,17 +91,7 @@ void conn_check_free (NiceAgent *agent); void conn_check_schedule_next (NiceAgent *agent); int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair); gboolean conn_check_prune_stream (NiceAgent *agent, guint stream_id); -gboolean conn_check_handle_inbound_stun (NiceAgent *agent, Stream *stream, Component *component, const NiceAddress *from, gchar *buf, guint len); - -/* functions using the old STUN API: - * ---------------------------------*/ - -void conn_check_handle_inbound_stun_old ( - NiceAgent *agent, - Stream *stream, - Component *component, - NiceCandidate *local, - NiceAddress from, - StunMessage *msg); +gboolean conn_check_handle_inbound_stun (NiceAgent *agent, Stream *stream, Component *component, NiceUDPSocket *udp_socket, const NiceAddress *from, gchar *buf, guint len); +gint conn_check_compare (const CandidateCheckPair *a, const CandidateCheckPair *b); #endif /*_NICE_CONNCHECK_H */ diff --git a/agent/discovery.c b/agent/discovery.c index c763a38..8419c05 100644 --- a/agent/discovery.c +++ b/agent/discovery.c @@ -33,13 +33,18 @@ * file under either the MPL or the LGPL. */ -#include <string.h> -#include <errno.h> +/** + * @file discovery.c + * @brief ICE candidate discovery functions + */ -#ifndef _BSD_SOURCE -#error "timercmp() macros needed" +#ifdef HAVE_CONFIG_H +# include <config.h> #endif -#include <sys/time.h> /* timercmp() macro, BSD */ + +#include <stdlib.h> +#include <string.h> +#include <errno.h> #include <netinet/in.h> #include <arpa/inet.h> @@ -52,6 +57,13 @@ #include "component.h" #include "discovery.h" +static inline int priv_timer_expired (GTimeVal *restrict timer, GTimeVal *restrict now) +{ + return (now->tv_sec == timer->tv_sec) ? + now->tv_usec >= timer->tv_usec : + now->tv_sec >= timer->tv_sec; +} + /** * Frees the CandidateDiscovery structure pointed to * by 'user data'. Compatible with g_slist_foreach(). @@ -74,12 +86,11 @@ void discovery_free (NiceAgent *agent) g_slist_free (agent->discovery_list), agent->discovery_list = NULL; - if (agent->discovery_timer_id) - g_source_remove (agent->discovery_timer_id), - agent->discovery_timer_id = 0; - agent->discovery_unsched_items = 0; } + if (agent->discovery_timer_id) + g_source_remove (agent->discovery_timer_id), + agent->discovery_timer_id = 0; } /** @@ -112,9 +123,45 @@ gboolean discovery_prune_stream (NiceAgent *agent, guint stream_id) i = i->next; } - /* return FALSE if there was a memory allocation failure */ - if (agent->conncheck_list == NULL && i != NULL) - return FALSE; + if (agent->discovery_list == NULL) { + /* return FALSE if there was a memory allocation failure */ + if (i != NULL) + return FALSE; + /* noone using the timer anymore, clean it up */ + discovery_free (agent); + } + + return TRUE; +} + +/** + * Adds a new local candidate. Implements the candidate pruning + * defined in ICE spec section 4.1.1.3 (ID-16). + */ +static gboolean priv_add_local_candidate_pruned (Component *component, NiceCandidate *candidate) +{ + GSList *modified_list, *i; + + for (i = component->local_candidates; i ; i = i->next) { + NiceCandidate *c = i->data; + + if (nice_address_equal (&c->base_addr, &candidate->base_addr) && + nice_address_equal (&c->addr, &candidate->addr)) { + g_debug ("Candidate %p (component-id %u) redundant, ignoring.", candidate, component->id); + return FALSE; + } + } + + modified_list= g_slist_append (component->local_candidates, + candidate); + if (modified_list) { + component->local_candidates = modified_list; + + /* note: candidate username and password are left NULL as stream + level ufrag/password are used */ + g_assert (candidate->username == NULL); + g_assert (candidate->password == NULL); + } return TRUE; } @@ -135,6 +182,7 @@ NiceCandidate *discovery_add_local_host_candidate ( Component *component; Stream *stream; NiceUDPSocket *udp_socket = NULL; + gboolean errors = FALSE; if (!agent_find_component (agent, stream_id, component_id, &stream, &component)) return NULL; @@ -143,49 +191,51 @@ NiceCandidate *discovery_add_local_host_candidate ( if (candidate) { NiceUDPSocket *udp_socket = g_slice_new0 (NiceUDPSocket); if (udp_socket) { - candidate->foundation = g_strdup_printf ("%u", agent->next_candidate_id++); + /* XXX: implement the foundation assignment as defined in + * ICE sect 4.1.1.4 ID-15: */ + g_snprintf (candidate->foundation, NICE_CANDIDATE_MAX_FOUNDATION, "%u", agent->next_candidate_id++); candidate->stream_id = stream_id; candidate->component_id = component_id; candidate->addr = *address; candidate->base_addr = *address; candidate->priority = nice_candidate_ice_priority (candidate); - /* note: username and password set to NULL as stream - ufrag/password are used */ + /* note: candidate username and password are left NULL as stream + level ufrag/password are used */ if (nice_udp_socket_factory_make (agent->socket_factory, udp_socket, address)) { - component->local_candidates = g_slist_append (component->local_candidates, - candidate); - if (component->local_candidates) { - component->sockets = g_slist_append (component->sockets, udp_socket); - if (component->sockets) { + + gboolean result = priv_add_local_candidate_pruned (component, candidate); + if (result == TRUE) { + GSList *modified_list = g_slist_append (component->sockets, udp_socket); + if (modified_list) { /* success: store a pointer to the sockaddr */ + component->sockets = modified_list; candidate->sockptr = udp_socket; candidate->addr = udp_socket->addr; candidate->base_addr = udp_socket->addr; agent_signal_new_candidate (agent, candidate); } else { /* error: list memory allocation */ - candidate = NULL; - /* note: candidate already owner by component */ + candidate = NULL; /* note: candidate already owned by component */ } } - else { /* error: memory alloc / list */ - nice_candidate_free (candidate), candidate = NULL; - } - } - else { /* error: socket factory make */ - nice_candidate_free (candidate), candidate = NULL; + else /* error: memory allocation, or duplicate candidatet */ + errors = TRUE; } + else /* error: socket factory make */ + errors = TRUE; } else /* error: udp socket memory allocation */ - nice_candidate_free (candidate), candidate = NULL; + errors = TRUE; } - if (!candidate) { - /* clean up after errors */ + /* clean up after errors */ + if (errors) { + if (candidate) + nice_candidate_free (candidate), candidate = NULL; if (udp_socket) g_slice_free (NiceUDPSocket, udp_socket); } @@ -199,7 +249,6 @@ NiceCandidate *discovery_add_local_host_candidate ( * * @return pointer to the created candidate, or NULL on error */ - NiceCandidate* discovery_add_server_reflexive_candidate ( NiceAgent *agent, @@ -211,40 +260,148 @@ discovery_add_server_reflexive_candidate ( NiceCandidate *candidate; Component *component; Stream *stream; + gboolean result = FALSE; if (!agent_find_component (agent, stream_id, component_id, &stream, &component)) return NULL; candidate = nice_candidate_new (NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE); if (candidate) { - candidate->foundation = g_strdup_printf ("%u", agent->next_candidate_id++); + candidate->priority = + nice_candidate_ice_priority_full + (NICE_CANDIDATE_TYPE_PREF_SERVER_REFLEXIVE, 0, component_id); + g_snprintf (candidate->foundation, NICE_CANDIDATE_MAX_FOUNDATION, "%u", agent->next_candidate_id++); candidate->stream_id = stream_id; candidate->component_id = component_id; candidate->addr = *address; - candidate->base_addr = *address; /* step: link to the base candidate+socket */ candidate->sockptr = base_socket; candidate->base_addr = base_socket->addr; + result = priv_add_local_candidate_pruned (component, candidate); + if (result != TRUE) { + /* error: memory allocation, or duplicate candidatet */ + nice_candidate_free (candidate), candidate = NULL; + } + } + + return candidate; +} + +/** + * Creates a peer reflexive candidate for 'component_id' of stream + * 'stream_id'. + * + * @return pointer to the created candidate, or NULL on error + */ +NiceCandidate* +discovery_add_peer_reflexive_candidate ( + NiceAgent *agent, + guint stream_id, + guint component_id, + NiceAddress *address, + NiceUDPSocket *base_socket) +{ + NiceCandidate *candidate; + Component *component; + Stream *stream; + + if (!agent_find_component (agent, stream_id, component_id, &stream, &component)) + return NULL; + + candidate = nice_candidate_new (NICE_CANDIDATE_TYPE_PEER_REFLEXIVE); + if (candidate) { + gboolean result; + + candidate->transport = NICE_CANDIDATE_TRANSPORT_UDP; candidate->priority = - 0x1000000 * 125 + 0x100 * 0 + 256 - component_id; /* sect:4.1.2.1(-14) */ - - component->local_candidates = g_slist_append (component->local_candidates, - candidate); - if (component->local_candidates) { - /* note: username and password left to NULL as stream-evel - * credentials are used by default */ + nice_candidate_ice_priority_full + (NICE_CANDIDATE_TYPE_PREF_PEER_REFLEXIVE, 0, component_id); + candidate->stream_id = stream_id; + candidate->component_id = component_id; + g_snprintf (candidate->foundation, NICE_CANDIDATE_MAX_FOUNDATION, "%u", agent->next_candidate_id++); + candidate->addr = *address; + candidate->base_addr = base_socket->addr; + + /* step: link to the base candidate+socket */ + candidate->sockptr = base_socket; + candidate->base_addr = base_socket->addr; + + result = priv_add_local_candidate_pruned (component, candidate); + if (result != TRUE) { + /* error: memory allocation, or duplicate candidatet */ + nice_candidate_free (candidate), candidate = NULL; + } + } + + return candidate; +} + +static guint priv_highest_remote_foundation (Component *component) +{ + GSList *i; + guint highest = 0; + + for (i = component->remote_candidates; i; i = i->next) { + NiceCandidate *cand = i->data; + guint foundation_id = (guint)atoi (cand->foundation); + if (foundation_id > highest) + highest = foundation_id; + } + + return highest; +} + +/** + * Adds a new peer reflexive candidate to the list of known + * remote candidates. The candidate is however not paired with + * existing local candidates. + * + * See ICE ID-16 sect 7.2.1.3. + * + * @return pointer to the created candidate, or NULL on error + */ +NiceCandidate *discovery_learn_remote_peer_reflexive_candidate ( + NiceAgent *agent, + Stream *stream, + Component *component, + guint32 priority, + const NiceAddress *remote_address, + NiceUDPSocket *udp_socket) +{ + NiceCandidate *candidate; + + candidate = nice_candidate_new (NICE_CANDIDATE_TYPE_PEER_REFLEXIVE); + if (candidate) { + GSList *modified_list; + + guint next_remote_id = priv_highest_remote_foundation (component); + + candidate->transport = NICE_CANDIDATE_TRANSPORT_UDP; + candidate->addr = *remote_address; + candidate->base_addr = *remote_address; + candidate->priority = priority;; + candidate->stream_id = stream->id; + candidate->component_id = component->id; + g_snprintf (candidate->foundation, NICE_CANDIDATE_MAX_FOUNDATION, "%u", next_remote_id); + candidate->sockptr = NULL; /* not stored for remote candidates */ + /* note: candidate username and password are left NULL as stream + level ufrag/password are used */ - g_assert (candidate->username == NULL); - g_assert (candidate->password == NULL); + modified_list = g_slist_append (component->remote_candidates, + candidate); + if (modified_list) { + component->remote_candidates = modified_list; + agent_signal_new_remote_candidate (agent, candidate); } - else /* error: memory allocation - list */ + else { /* error: memory alloc / list */ nice_candidate_free (candidate), candidate = NULL; + } } return candidate; - } +} /** * Timer callback that handles scheduling new candidate discovery @@ -262,10 +419,10 @@ static gboolean priv_discovery_tick (gpointer pointer) GSList *i; int not_done = 0; /* note: track whether to continue timer */ -#ifdef DEBUG +#ifndef NDEBUG { static int tick_counter = 0; - if (++tick_counter % 20 == 0) + if (++tick_counter % 1 == 0) g_debug ("discovery tick #%d with list %p (1)", tick_counter, agent->discovery_list); } #endif @@ -288,9 +445,18 @@ static gboolean priv_discovery_tick (gpointer pointer) int res; memset (&stun_server, 0, sizeof(stun_server)); - + + if (strchr (cand->server_addr, ':') == NULL) + stun_server.sin_family = AF_INET; + else + stun_server.sin_family = AF_INET6; stun_server.sin_addr.s_addr = inet_addr(cand->server_addr); - stun_server.sin_port = htons(IPPORT_STUN); + stun_server.sin_port = htons(cand->server_port); + + agent_signal_component_state_change (agent, + cand->stream->id, + cand->component->id, + NICE_COMPONENT_STATE_GATHERING); res = stun_bind_start (&cand->stun_ctx, cand->socket, (struct sockaddr*)&stun_server, sizeof(stun_server)); @@ -299,11 +465,6 @@ static gboolean priv_discovery_tick (gpointer pointer) /* case: success, start waiting for the result */ g_get_current_time (&cand->next_tick); - agent_signal_component_state_change (agent, - cand->stream->id, - cand->component->id, - NICE_COMPONENT_STATE_GATHERING); - } else { /* case: error in starting discovery, start the next discovery */ @@ -329,7 +490,7 @@ static gboolean priv_discovery_tick (gpointer pointer) cand->done = TRUE; } /* note: macro from sys/time.h but compatible with GTimeVal */ - else if (timercmp(&cand->next_tick, &now, <=)) { + else if (priv_timer_expired (&cand->next_tick, &now)) { int res = stun_bind_elapse (cand->stun_ctx); if (res == EAGAIN) { /* case: not ready complete, so schedule next timeout */ @@ -339,11 +500,6 @@ static gboolean priv_discovery_tick (gpointer pointer) g_get_current_time (&cand->next_tick); g_time_val_add (&cand->next_tick, timeout * 10); - /* note: macro from sys/time.h but compatible with GTimeVal */ - if (timercmp(&cand->next_tick, &agent->next_check_tv, <)) { - agent->next_check_tv = cand->next_tick; - } - ++not_done; /* note: retry later */ } else { @@ -373,7 +529,8 @@ static gboolean priv_discovery_tick (gpointer pointer) } /** - * Initiates the active candidate discovery process. + * Initiates the candidate discovery process by starting + * the necessary timers. * * @pre agent->discovery_list != NULL // unsched discovery items available */ @@ -381,23 +538,15 @@ void discovery_schedule (NiceAgent *agent) { g_assert (agent->discovery_list != NULL); - g_debug ("Scheduling discovery..."); - if (agent->discovery_unsched_items > 0) { - /* XXX: make timeout Ta configurable */ - guint next = NICE_AGENT_TIMER_TA_DEFAULT; - - /* XXX: send a component state-change, but, but, how do we - * actually do this? back to the drawing board... */ - - /* step 1: run first iteration immediately */ - priv_discovery_tick (agent); - - g_debug ("Scheduling a discovery timeout of %u msec.", next); - - /* step 2: scheduling timer */ - agent->discovery_timer_id = - g_timeout_add (next, priv_discovery_tick, agent); + if (agent->discovery_timer_id == 0) { + /* step: run first iteration immediately */ + gboolean res = priv_discovery_tick (agent); + if (res == TRUE) { + agent->discovery_timer_id = + g_timeout_add (agent->timer_ta, priv_discovery_tick, agent); + } + } } } diff --git a/agent/discovery.h b/agent/discovery.h index 1fb18be..a30ae53 100644 --- a/agent/discovery.h +++ b/agent/discovery.h @@ -50,6 +50,7 @@ struct _CandidateDiscovery guint socket; /**< XXX: should be taken from local cand: existing socket to use */ NiceUDPSocket *nicesock; /**< XXX: should be taken from local cand: existing socket to use */ const gchar *server_addr; /**< STUN/TURN server address, not owned */ + guint server_port; /**< STUN/TURN server port */ NiceAddress *interface; /**< Address of local interface */ stun_bind_t *stun_ctx; GTimeVal next_tick; /**< next tick timestamp */ @@ -64,7 +65,8 @@ void discovery_free (NiceAgent *agent); gboolean discovery_prune_stream (NiceAgent *agent, guint stream_id); void discovery_schedule (NiceAgent *agent); -NiceCandidate *discovery_add_local_host_candidate ( +NiceCandidate * +discovery_add_local_host_candidate ( NiceAgent *agent, guint stream_id, guint component_id, @@ -78,4 +80,21 @@ discovery_add_server_reflexive_candidate ( NiceAddress *address, NiceUDPSocket *base_socket); +NiceCandidate* +discovery_add_peer_reflexive_candidate ( + NiceAgent *agent, + guint stream_id, + guint component_id, + NiceAddress *address, + NiceUDPSocket *base_socket); + +NiceCandidate * +discovery_learn_remote_peer_reflexive_candidate ( + NiceAgent *agent, + Stream *stream, + Component *component, + guint32 priority, + const NiceAddress *remote_address, + NiceUDPSocket *udp_socket); + #endif /*_NICE_CONNCHECK_H */ diff --git a/agent/stream.c b/agent/stream.c index 7d746a1..377e4a1 100644 --- a/agent/stream.c +++ b/agent/stream.c @@ -34,11 +34,19 @@ * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif #include <string.h> #include "stream.h" +/** + * @file stream.c + * @brief ICE stream functionality + */ + Stream * stream_new (void) { diff --git a/agent/stream.h b/agent/stream.h index fb95e9a..5c7be1c 100644 --- a/agent/stream.h +++ b/agent/stream.h @@ -44,10 +44,13 @@ G_BEGIN_DECLS -/* Following do not including the terminating NULL */ +/* Following include the terminating NULL */ -#define NICE_STREAM_MAX_UFRAG_LEN 4 -#define NICE_STREAM_MAX_PWD_LEN 22 +#define NICE_STREAM_MAX_UFRAG 1024 + 1 +#define NICE_STREAM_MAX_UNAME 1024 + 1024 + 1 + 1 /* colon plus NULL */ +#define NICE_STREAM_MAX_PWD 1024 + 1 +#define NICE_STREAM_DEF_UFRAG 4 + 1 +#define NICE_STREAM_DEF_PWD 22 + 1 typedef struct _Stream Stream; @@ -58,10 +61,10 @@ struct _Stream gboolean initial_binding_request_received; /* XXX: streams can have multiple components */ Component *component; - gchar local_ufrag[NICE_STREAM_MAX_UFRAG_LEN + 1]; - gchar local_password[NICE_STREAM_MAX_PWD_LEN + 1]; - gchar remote_ufrag[NICE_STREAM_MAX_UFRAG_LEN + 1]; - gchar remote_password[NICE_STREAM_MAX_PWD_LEN + 1]; + gchar local_ufrag[NICE_STREAM_MAX_UFRAG]; + gchar local_password[NICE_STREAM_MAX_PWD]; + gchar remote_ufrag[NICE_STREAM_MAX_UFRAG]; + gchar remote_password[NICE_STREAM_MAX_PWD]; }; Stream * diff --git a/agent/test-add-remove-stream.c b/agent/test-add-remove-stream.c index 65d3b2c..6da7626 100644 --- a/agent/test-add-remove-stream.c +++ b/agent/test-add-remove-stream.c @@ -34,6 +34,9 @@ * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif #include "agent.h" #include "agent-priv.h" diff --git a/agent/test-fullmode.c b/agent/test-fullmode.c index 43c3f0d..7384547 100644 --- a/agent/test-fullmode.c +++ b/agent/test-fullmode.c @@ -32,6 +32,9 @@ * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif #include <stdlib.h> #include <string.h> @@ -44,36 +47,26 @@ static const guint test_component_id = 1; static NiceComponentState global_lagent_state = NICE_COMPONENT_STATE_LAST; static NiceComponentState global_ragent_state = NICE_COMPONENT_STATE_LAST; static GMainLoop *global_mainloop = NULL; -static gboolean global_candidate_gathering_done = FALSE; +static gboolean global_lagent_gathering_done = FALSE; +static gboolean global_ragent_gathering_done = FALSE; static gboolean global_lagent_ibr_received = FALSE; static gboolean global_ragent_ibr_received = FALSE; static int global_lagent_cands = 0; static int global_ragent_cands = 0; +static gint global_ragent_read = 0; -static gboolean timer_cb (gpointer pointer) +static void priv_print_global_status (void) { - g_debug ("%s: %p", G_STRFUNC, pointer); - - /* signal status via a global variable */ - - g_debug ("\tgathering_done=%d", global_candidate_gathering_done); + g_debug ("\tgathering_done=%d", global_lagent_gathering_done && global_ragent_gathering_done); g_debug ("\tlstate=%d", global_lagent_state); g_debug ("\trstate=%d", global_ragent_state); +} + +static gboolean timer_cb (gpointer pointer) +{ + g_debug ("%s: %p", G_STRFUNC, pointer); - if (global_candidate_gathering_done != TRUE && - global_lagent_state == NICE_COMPONENT_STATE_GATHERING && - global_ragent_state == NICE_COMPONENT_STATE_GATHERING) { - global_candidate_gathering_done = TRUE; - g_main_loop_quit (global_mainloop); - return TRUE; - } /* signal status via a global variable */ - else if (global_candidate_gathering_done == TRUE && - global_lagent_state == NICE_COMPONENT_STATE_READY && - global_ragent_state == NICE_COMPONENT_STATE_READY) { - g_main_loop_quit (global_mainloop); - return TRUE; - } /* note: should not be reached, abort */ g_debug ("ERROR: test has got stuck, aborting..."); @@ -86,8 +79,12 @@ static void cb_nice_recv (NiceAgent *agent, guint stream_id, guint component_id, g_debug ("%s: %p", G_STRFUNC, user_data); /* XXX: dear compiler, these are for you: */ - (void)agent; (void)stream_id; (void)component_id; (void)len; (void)buf; - (void)user_data; + (void)agent; (void)stream_id; (void)component_id; (void)buf; + + if ((int)user_data == 2) { + global_ragent_read = len; + g_main_loop_quit (global_mainloop); + } } static void cb_candidate_gathering_done(NiceAgent *agent, gpointer data) @@ -95,9 +92,13 @@ static void cb_candidate_gathering_done(NiceAgent *agent, gpointer data) g_debug ("%s: %p", G_STRFUNC, data); if ((int)data == 1) - global_lagent_state = NICE_COMPONENT_STATE_GATHERING; + global_lagent_gathering_done = TRUE; else if ((int)data == 2) - global_ragent_state = NICE_COMPONENT_STATE_GATHERING; + global_ragent_gathering_done = TRUE; + + if (global_lagent_gathering_done && + global_ragent_gathering_done) + g_main_loop_quit (global_mainloop); /* XXX: dear compiler, these are for you: */ (void)agent; @@ -114,6 +115,20 @@ static void cb_component_state_changed(NiceAgent *agent, guint stream_id, guint else if ((int)data == 2) global_ragent_state = state; + /* signal status via a global variable */ + if (global_lagent_state == NICE_COMPONENT_STATE_READY && + global_ragent_state == NICE_COMPONENT_STATE_READY) { + g_main_loop_quit (global_mainloop); + return; + } + + /* signal status via a global variable */ + if (global_lagent_state == NICE_COMPONENT_STATE_FAILED && + global_ragent_state == NICE_COMPONENT_STATE_FAILED) { + g_main_loop_quit (global_mainloop); + return; + } + /* XXX: dear compiler, these are for you: */ (void)agent; (void)stream_id; (void)data; } @@ -159,7 +174,7 @@ static int run_full_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *bas { NiceAddress laddr, raddr; NiceCandidateDesc cdes = { /* candidate description (no ports) */ - "1", /* foundation */ + (gchar *)"1", /* foundation */ test_component_id, NICE_CANDIDATE_TRANSPORT_UDP, /* transport */ 100000, /* priority */ @@ -170,20 +185,33 @@ static int run_full_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *bas GSList *cands, *i; guint ls_id, rs_id; + global_lagent_state = NICE_COMPONENT_STATE_LAST; + global_ragent_state = NICE_COMPONENT_STATE_LAST; + global_lagent_gathering_done = FALSE; + global_ragent_gathering_done = FALSE; + global_lagent_ibr_received = + global_ragent_ibr_received = FALSE; + global_lagent_cands = + global_ragent_cands = 0; + + g_object_set (G_OBJECT (lagent), "controlling-mode", TRUE, NULL); + g_object_set (G_OBJECT (ragent), "controlling-mode", FALSE, NULL); + /* step: add one stream, with one component, to each agent */ ls_id = nice_agent_add_stream (lagent, 1); rs_id = nice_agent_add_stream (ragent, 1); g_assert (ls_id > 0); g_assert (rs_id > 0); - g_object_set (G_OBJECT (lagent), "controlling-mode", TRUE, NULL); - g_object_set (G_OBJECT (lagent), "controlling-mode", FALSE, NULL); - /* step: run mainloop until local candidates are ready * (see timer_cb() above) */ - g_debug ("test-fullmode: Added streams, running mainloop until 'candidate-gathering-done'..."); - g_main_loop_run (global_mainloop); - g_assert (global_candidate_gathering_done == TRUE); + if (global_lagent_gathering_done != TRUE || + global_ragent_gathering_done != TRUE) { + g_debug ("test-fullmode: Added streams, running mainloop until 'candidate-gathering-done'..."); + g_main_loop_run (global_mainloop); + g_assert (global_lagent_gathering_done == TRUE); + g_assert (global_ragent_gathering_done == TRUE); + } /* step: find out the local candidates of each agent */ cands = nice_agent_get_local_candidates(lagent, ls_id, NICE_COMPONENT_TYPE_RTP); @@ -238,6 +266,214 @@ static int run_full_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *bas g_assert (global_lagent_cands == 1); g_assert (global_ragent_cands == 1); + /* note: test payload send and receive */ + global_ragent_read = 0; + g_assert (nice_agent_send (lagent, ls_id, 1, 16, "1234567812345678") == 16); + g_main_loop_run (global_mainloop); + g_assert (global_ragent_read == 16); + + g_debug ("test-fullmode: Ran mainloop, removing streams..."); + + /* step: clean up resources and exit */ + + nice_agent_remove_stream (lagent, ls_id); + nice_agent_remove_stream (ragent, rs_id); + + return 0; +} + +static int run_full_test_wrong_password (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *baseaddr) +{ + NiceAddress laddr, raddr; + NiceCandidateDesc cdes = { /* candidate description (no ports) */ + (gchar *)"1", /* foundation */ + test_component_id, + NICE_CANDIDATE_TRANSPORT_UDP, /* transport */ + 100000, /* priority */ + NULL, /* address */ + NICE_CANDIDATE_TYPE_HOST, /* type */ + NULL /* base-address */ + }; + GSList *cands, *i; + guint ls_id, rs_id; + + global_lagent_state = + global_ragent_state = NICE_COMPONENT_STATE_LAST; + global_lagent_gathering_done = + global_ragent_gathering_done = FALSE; + global_lagent_cands = + global_ragent_cands = 0; + + g_object_set (G_OBJECT (lagent), "controlling-mode", TRUE, NULL); + g_object_set (G_OBJECT (ragent), "controlling-mode", FALSE, NULL); + + /* step: add one stream, with one component, to each agent */ + ls_id = nice_agent_add_stream (lagent, 1); + rs_id = nice_agent_add_stream (ragent, 1); + g_assert (ls_id > 0); + g_assert (rs_id > 0); + + /* step: run mainloop until local candidates are ready + * (see timer_cb() above) */ + if (global_lagent_gathering_done != TRUE || + global_ragent_gathering_done != TRUE) { + g_debug ("test-fullmode: Added streams, running mainloop until 'candidate-gathering-done'..."); + g_main_loop_run (global_mainloop); + g_assert (global_lagent_gathering_done == TRUE); + g_assert (global_ragent_gathering_done == TRUE); + } + + /* step: find out the local candidates of each agent */ + cands = nice_agent_get_local_candidates(lagent, ls_id, NICE_COMPONENT_TYPE_RTP); + for (i = cands; i; i = i->next) { + NiceCandidate *cand = i->data; + if (cand) { + g_debug ("test-fullmode: local port L %u", cand->addr.port); + laddr = cand->addr; + } + } + g_slist_free (cands); + + cands = nice_agent_get_local_candidates(ragent, rs_id, NICE_COMPONENT_TYPE_RTP); + for (i = cands; i; i = i->next) { + NiceCandidate *cand = i->data; + if (cand) { + g_debug ("test-fullmode: local port R %u", cand->addr.port); + raddr = cand->addr; + } + } + g_slist_free (cands); + g_debug ("test-fullmode: Got local candidates..."); + + /* step: pass the remote candidates to agents */ + cands = g_slist_append (NULL, &cdes); + { + const gchar *ufrag = NULL, *password = NULL; + nice_agent_get_local_credentials(lagent, ls_id, &ufrag, &password); + nice_agent_set_remote_credentials (ragent, + rs_id, "wrong", password); + nice_agent_get_local_credentials(ragent, rs_id, &ufrag, &password); + nice_agent_set_remote_credentials (lagent, + ls_id, ufrag, "wrong2"); + } + cdes.addr = &raddr; + nice_agent_set_remote_candidates (lagent, ls_id, NICE_COMPONENT_TYPE_RTP, cands); + cdes.addr = &laddr; + nice_agent_set_remote_candidates (ragent, rs_id, NICE_COMPONENT_TYPE_RTP, cands); + g_slist_free (cands); + + g_debug ("test-fullmode: Set properties, next running mainloop until connectivity checks succeed..."); + + /* step: run the mainloop until connectivity checks succeed + * (see timer_cb() above) */ + g_main_loop_run (global_mainloop); + + /* note: verify that correct number of local candidates were reported */ + g_assert (global_lagent_cands == 0); + g_assert (global_ragent_cands == 0); + + g_debug ("test-fullmode: Ran mainloop, removing streams..."); + + /* step: clean up resources and exit */ + + nice_agent_remove_stream (lagent, ls_id); + nice_agent_remove_stream (ragent, rs_id); + + return 0; +} + +static int run_full_test_control_conflict (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *baseaddr, gboolean role) +{ + NiceAddress laddr, raddr; + NiceCandidateDesc cdes = { /* candidate description (no ports) */ + (gchar *)"1", /* foundation */ + test_component_id, + NICE_CANDIDATE_TRANSPORT_UDP, /* transport */ + 100000, /* priority */ + NULL, /* address */ + NICE_CANDIDATE_TYPE_HOST, /* type */ + NULL /* base-address */ + }; + GSList *cands, *i; + guint ls_id, rs_id; + + global_lagent_state = + global_ragent_state = NICE_COMPONENT_STATE_LAST; + global_lagent_gathering_done = + global_ragent_gathering_done = FALSE; + global_lagent_cands = + global_ragent_cands = 0; + global_lagent_ibr_received = + global_ragent_ibr_received = FALSE; + + g_object_set (G_OBJECT (lagent), "controlling-mode", role, NULL); + g_object_set (G_OBJECT (ragent), "controlling-mode", role, NULL); + + /* step: add one stream, with one component, to each agent */ + ls_id = nice_agent_add_stream (lagent, 1); + rs_id = nice_agent_add_stream (ragent, 1); + g_assert (ls_id > 0); + g_assert (rs_id > 0); + + /* step: run mainloop until local candidates are ready + * (see timer_cb() above) */ + if (global_lagent_gathering_done != TRUE || + global_ragent_gathering_done != TRUE) { + g_debug ("test-fullmode: Added streams, running mainloop until 'candidate-gathering-done'..."); + g_main_loop_run (global_mainloop); + g_assert (global_lagent_gathering_done == TRUE); + g_assert (global_ragent_gathering_done == TRUE); + } + + /* step: find out the local candidates of each agent */ + cands = nice_agent_get_local_candidates(lagent, ls_id, NICE_COMPONENT_TYPE_RTP); + for (i = cands; i; i = i->next) { + NiceCandidate *cand = i->data; + if (cand) { + g_debug ("test-fullmode: local port L %u", cand->addr.port); + laddr = cand->addr; + } + } + g_slist_free (cands); + + cands = nice_agent_get_local_candidates(ragent, rs_id, NICE_COMPONENT_TYPE_RTP); + for (i = cands; i; i = i->next) { + NiceCandidate *cand = i->data; + if (cand) { + g_debug ("test-fullmode: local port R %u", cand->addr.port); + raddr = cand->addr; + } + } + g_slist_free (cands); + g_debug ("test-fullmode: Got local candidates..."); + + /* step: pass the remote candidates to agents */ + cands = g_slist_append (NULL, &cdes); + { + const gchar *ufrag = NULL, *password = NULL; + nice_agent_get_local_credentials(lagent, ls_id, &ufrag, &password); + nice_agent_set_remote_credentials (ragent, + rs_id, ufrag, password); + nice_agent_get_local_credentials(ragent, rs_id, &ufrag, &password); + nice_agent_set_remote_credentials (lagent, + ls_id, ufrag, password); + } + cdes.addr = &raddr; + nice_agent_set_remote_candidates (lagent, ls_id, NICE_COMPONENT_TYPE_RTP, cands); + cdes.addr = &laddr; + nice_agent_set_remote_candidates (ragent, rs_id, NICE_COMPONENT_TYPE_RTP, cands); + g_slist_free (cands); + + g_debug ("test-fullmode: Set properties, next running mainloop until connectivity checks succeed..."); + + /* step: run the mainloop until connectivity checks succeed + * (see timer_cb() above) */ + g_main_loop_run (global_mainloop); + + /* note: verify that correct number of local candidates were reported */ + g_assert (global_lagent_cands == 1); + g_assert (global_ragent_cands == 1); + g_debug ("test-fullmode: Ran mainloop, removing streams..."); /* step: clean up resources and exit */ @@ -255,6 +491,7 @@ int main (void) NiceAddress baseaddr; int result; guint timer_id; + const char *stun_server = NULL, *stun_server_port = NULL; g_type_init (); global_mainloop = g_main_loop_new (NULL, FALSE); @@ -271,11 +508,11 @@ int main (void) ragent = nice_agent_new (&udpfactory); /* step: attach to mainloop (needed to register the fds) */ - nice_agent_main_context_attach (lagent, g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL); - nice_agent_main_context_attach (ragent, g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL); + nice_agent_main_context_attach (lagent, g_main_loop_get_context (global_mainloop), cb_nice_recv, (gpointer)1); + nice_agent_main_context_attach (ragent, g_main_loop_get_context (global_mainloop), cb_nice_recv, (gpointer)2); /* step: add a timer to catch state changes triggered by signals */ - timer_id = g_timeout_add (2000, timer_cb, NULL); + timer_id = g_timeout_add (30000, timer_cb, NULL); /* step: specify which local interface to use */ if (!nice_address_set_ipv4_from_string (&baseaddr, "127.0.0.1")) @@ -304,27 +541,69 @@ int main (void) g_signal_connect (G_OBJECT (ragent), "initial-binding-request-received", G_CALLBACK (cb_initial_binding_request_received), (gpointer)2); - g_debug ("test-fullmode: running test for the 1st time"); - result = run_full_test (lagent, ragent, &baseaddr); + stun_server = getenv ("NICE_STUN_SERVER"); + stun_server_port = getenv ("NICE_STUN_SERVER_PORT"); + if (stun_server) { + g_object_set (G_OBJECT (lagent), "stun-server", stun_server, NULL); + g_object_set (G_OBJECT (lagent), "stun-server-port", atoi (stun_server_port), NULL); + g_object_set (G_OBJECT (ragent), "stun-server", stun_server, NULL); + g_object_set (G_OBJECT (ragent), "stun-server-port", atoi (stun_server_port), NULL); + } + + { + gpointer pointer; + gchar *string = NULL; + guint port = 0; + gboolean mode = FALSE; + g_object_get (G_OBJECT (lagent), "socket-factory", &pointer, NULL); + g_assert (pointer == (gpointer)&udpfactory); + g_object_get (G_OBJECT (lagent), "stun-server", &string, NULL); + g_assert (stun_server == NULL || strcmp (string, stun_server) == 0); + g_object_get (G_OBJECT (lagent), "stun-server-port", &port, NULL); + g_assert (stun_server_port == NULL || port == (guint)atoi (stun_server_port)); + g_object_get (G_OBJECT (lagent), "turn-server", &string, NULL); + g_object_get (G_OBJECT (lagent), "turn-server-port", &port, NULL); + g_object_get (G_OBJECT (lagent), "controlling-mode", &mode, NULL); + g_assert (mode == TRUE); + } - /* step: check results of first run */ + /* step: run test the first time */ + g_debug ("test-fullmode: TEST STARTS / running test for the 1st time"); + result = run_full_test (lagent, ragent, &baseaddr); + priv_print_global_status (); g_assert (result == 0); g_assert (global_lagent_state == NICE_COMPONENT_STATE_READY); g_assert (global_ragent_state == NICE_COMPONENT_STATE_READY); /* step: run test again without unref'ing agents */ + g_debug ("test-fullmode: TEST STARTS / running test for the 2nd time"); + result = run_full_test (lagent, ragent, &baseaddr); + priv_print_global_status (); + g_assert (result == 0); + g_assert (global_lagent_state == NICE_COMPONENT_STATE_READY); + g_assert (global_ragent_state == NICE_COMPONENT_STATE_READY); - global_lagent_state = NICE_COMPONENT_STATE_LAST; - global_ragent_state = NICE_COMPONENT_STATE_LAST; - global_candidate_gathering_done = FALSE; - global_lagent_ibr_received = - global_ragent_ibr_received = FALSE; - global_lagent_cands = - global_ragent_cands = 0; + /* run test with incorrect credentials (make sure process fails) */ + g_debug ("test-fullmode: TEST STARTS / incorrect credentials"); + result = run_full_test_wrong_password (lagent, ragent, &baseaddr); + priv_print_global_status (); + g_assert (result == 0); + g_assert (global_lagent_state == NICE_COMPONENT_STATE_FAILED); + g_assert (global_ragent_state == NICE_COMPONENT_STATE_FAILED); - g_debug ("test-fullmode: running test the 2nd time"); - result = run_full_test (lagent, ragent, &baseaddr); + /* run test with a conflict in controlling mode: controlling-controlling */ + g_debug ("test-fullmode: TEST STARTS / controlling mode conflict case-1"); + result = run_full_test_control_conflict (lagent, ragent, &baseaddr, TRUE); + priv_print_global_status (); + g_assert (result == 0); + g_assert (global_lagent_state == NICE_COMPONENT_STATE_READY); + g_assert (global_ragent_state == NICE_COMPONENT_STATE_READY); + /* run test with a conflict in controlling mode: controlled-controlled */ + g_debug ("test-fullmode: TEST STARTS / controlling mode conflict case-2"); + result = run_full_test_control_conflict (lagent, ragent, &baseaddr, FALSE); + priv_print_global_status (); + g_assert (result == 0); g_assert (global_lagent_state == NICE_COMPONENT_STATE_READY); g_assert (global_ragent_state == NICE_COMPONENT_STATE_READY); diff --git a/agent/test-mainloop.c b/agent/test-mainloop.c index 0a14ee7..9a5561a 100644 --- a/agent/test-mainloop.c +++ b/agent/test-mainloop.c @@ -35,6 +35,9 @@ * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif #include <string.h> diff --git a/agent/test-poll.c b/agent/test-poll.c index 92b1c5c..53710f2 100644 --- a/agent/test-poll.c +++ b/agent/test-poll.c @@ -35,6 +35,9 @@ * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif #include <string.h> @@ -73,6 +76,7 @@ main (void) gint pipe_fds[2]; GSList *fds = NULL; GSList *readable; + ssize_t w; memset (&addr, 0, sizeof (addr)); g_type_init (); @@ -102,7 +106,8 @@ main (void) if (pipe (pipe_fds) != 0) g_assert_not_reached (); - write (pipe_fds[1], "hello", 5); + w = write (pipe_fds[1], "hello", 5); + g_assert (w == 5); fds = g_slist_append (fds, GUINT_TO_POINTER (pipe_fds[0])); diff --git a/agent/test-priority.c b/agent/test-priority.c index 2854250..e8ee42a 100644 --- a/agent/test-priority.c +++ b/agent/test-priority.c @@ -34,6 +34,9 @@ * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif #include "agent.h" @@ -53,10 +56,10 @@ main (void) /* 2^32*MIN(O,A) + 2*MAX(O,A) + (O>A?1:0) = 2^32*1 + 2*5000 + 0 = 4294977296 */ - g_assert (nice_candidate_pair_priority (1,5000) == 4294977296); + g_assert (nice_candidate_pair_priority (1,5000) == 4294977296LL); /* 2^32*1 + 2*5000 + 1 = 4294977297 */ - g_assert (nice_candidate_pair_priority (5000, 1) == 4294977297); + g_assert (nice_candidate_pair_priority (5000, 1) == 4294977297LL); return 0; } diff --git a/agent/test-recv.c b/agent/test-recv.c index ee94f75..4c66944 100644 --- a/agent/test-recv.c +++ b/agent/test-recv.c @@ -35,6 +35,9 @@ * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif #include <string.h> diff --git a/agent/test-send.c b/agent/test-send.c index a214b2f..cee11bf 100644 --- a/agent/test-send.c +++ b/agent/test-send.c @@ -35,6 +35,9 @@ * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif #include <string.h> diff --git a/agent/test-stun.c b/agent/test-stun.c index 6fd74ea..f8f77c2 100644 --- a/agent/test-stun.c +++ b/agent/test-stun.c @@ -35,6 +35,9 @@ * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif #include <string.h> @@ -187,7 +190,7 @@ test_stun_valid_password ( bres = stun_message_new (STUN_MESSAGE_BINDING_RESPONSE, "0123456789abcdef", 2); bres->attributes[0] = stun_attribute_mapped_address_new ( - from.addr_ipv4, 5678); + from.addr.addr_ipv4, 5678); bres->attributes[1] = stun_attribute_username_new (username); packed_len = stun_message_pack (bres, &packed); stun_message_free (bres); diff --git a/agent/test.c b/agent/test.c index cb5e1ce..071b2b7 100644 --- a/agent/test.c +++ b/agent/test.c @@ -34,6 +34,9 @@ * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the LGPL. */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif #include <string.h> |