summaryrefslogtreecommitdiff
path: root/agent
diff options
context:
space:
mode:
authorKai Vehmanen <first.surname@nokia.com>2007-06-19 08:06:00 +0000
committerKai Vehmanen <first.surname@nokia.com>2007-06-19 08:06:00 +0000
commit181d9d56df9332544f72a856e51cff62f45a15ad (patch)
tree3d42a16ed0bd2ec9f98eac0645494110e1852adf /agent
parent99ff130b9bc44c75a30ee60078d1548d61f99270 (diff)
downloadlibnice-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.am9
-rw-r--r--agent/agent-priv.h10
-rw-r--r--agent/agent.c537
-rw-r--r--agent/agent.h23
-rw-r--r--agent/candidate.c27
-rw-r--r--agent/candidate.h7
-rwxr-xr-xagent/check-test-fullmode-with-stun.sh29
-rw-r--r--agent/component.c9
-rw-r--r--agent/component.h2
-rw-r--r--agent/conncheck.c1187
-rw-r--r--agent/conncheck.h24
-rw-r--r--agent/discovery.c303
-rw-r--r--agent/discovery.h21
-rw-r--r--agent/stream.c8
-rw-r--r--agent/stream.h17
-rw-r--r--agent/test-add-remove-stream.c3
-rw-r--r--agent/test-fullmode.c371
-rw-r--r--agent/test-mainloop.c3
-rw-r--r--agent/test-poll.c7
-rw-r--r--agent/test-priority.c7
-rw-r--r--agent/test-recv.c3
-rw-r--r--agent/test-send.c3
-rw-r--r--agent/test-stun.c5
-rw-r--r--agent/test.c3
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>