Commit 0636f9ad authored by Fabrice Bellet's avatar Fabrice Bellet

conncheck: implement ice regular nomination method

This patch implements Regular Nomation as described in RFC5245
8.1.1.1. The controlling agent lets valid pairs accumulate, and
decides which pair to recheck with the use-candidate attribute set.
priv_mark_pair_nominated() follows 7.2.1.5, to update the nominated
pair when acting as a STUN server, and
priv_map_reply_to_conn_check_request() implements 7.1.3.2.4 to
update the nominated pair when acting as a STUN client. A new
property is also added to the agent to control the nomination
mode, which can be regular of aggressive, with default value
set to aggressive.

Two new flags are introduced in the CandidateCheckPair structure:

- use_candidate_on_next_check indicates the STUN client to add the
  use-candidate attribute when the pair will be checked. At this
  time, the nominated flag has not been set on this pair yet.

- mark_nominated_on_response_arrival indicates the STUN server
  to nominate the pair when its succesfull response to a
  previous triggered check will arrive (7.2.1.5, item #2)

Differential Revision: https://phabricator.freedesktop.org/D811
parent a602ff57
......@@ -22,6 +22,12 @@ if WINDOWS
AM_CFLAGS += -DWINVER=0x0501 # _WIN32_WINNT_WINXP
endif
BUILT_SOURCES = \
agent-enum-types.h \
agent-enum-types.c
CLEANFILES += $(BUILT_SOURCES)
noinst_LTLIBRARIES = libagent.la
libagent_la_SOURCES = \
......@@ -54,6 +60,23 @@ libagent_la_SOURCES = \
outputstream.c \
$(BUILT_SOURCES)
agent-enum-types.h: agent.h Makefile
$(AM_V_GEN)$(GLIB_MKENUMS) \
--fhead "#ifndef __AGENT_ENUM_TYPES_H__\n#define __AGENT_ENUM_TYPES_H__ 1\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
--fprod "/* enumerations from \"@filename@\" */\n" \
--vhead "GType @enum_name@_get_type (void) G_GNUC_CONST;\n#define NICE_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \
--ftail "G_END_DECLS\n\n#endif /* !AGENT_ENUM_TYPES_H */" \
$(addprefix $(srcdir)/,agent.h) > $@
agent-enum-types.c: agent.h Makefile agent-enum-types.h
$(AM_V_GEN)$(GLIB_MKENUMS) \
--fhead "#include <config.h>\n#include <glib-object.h>\n#include \"agent.h\"\n#include \"agent-enum-types.h\"" \
--fprod "\n/* enumerations from \"@filename@\" */" \
--vhead "GType\n@enum_name@_get_type (void)\n{\n static GType type = 0;\n if (!type) {\n static const G@Type@Value values[] = {" \
--vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
--vtail " { 0, NULL, NULL }\n };\n type = g_@type@_register_static (\"@EnumName@\", values);\n }\n return type;\n}\n\n" \
$(addprefix $(srcdir)/,agent.h) > $@
libagent_la_LIBADD = \
$(top_builddir)/random/libnice-random.la \
$(top_builddir)/socket/libsocket.la \
......
......@@ -115,6 +115,13 @@ nice_input_message_iter_compare (const NiceInputMessageIter *a,
#define NICE_COMPONENT_MAX_VALID_CANDIDATES 50 /* maximum number of validates remote candidates to keep, the number is arbitrary but hopefully large enough */
/* A convenient macro to test if the agent is compatible with RFC5245
* or OC2007R2. Specifically these two modes share the support
* of the regular or aggressive nomination mode */
#define NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2(obj) \
((obj)->compatibility == NICE_COMPATIBILITY_RFC5245 || \
(obj)->compatibility == NICE_COMPATIBILITY_OC2007R2)
struct _NiceAgent
{
GObject parent; /* gobject pointer */
......@@ -134,6 +141,7 @@ struct _NiceAgent
guint stun_max_retransmissions; /* property: stun max retransmissions, Rc */
guint stun_initial_timeout; /* property: stun initial timeout, RTO */
guint stun_reliable_timeout; /* property: stun reliable timeout */
NiceNominationMode nomination_mode; /* property: Nomination mode */
GSList *local_addresses; /* list of NiceAddresses for local
interfaces */
......
......@@ -73,6 +73,7 @@
#include "interfaces.h"
#include "pseudotcp.h"
#include "agent-enum-types.h"
/* Maximum size of a UDP packet’s payload, as the packet’s length field is 16b
* wide. */
......@@ -116,6 +117,7 @@ enum
PROP_STUN_MAX_RETRANSMISSIONS,
PROP_STUN_INITIAL_TIMEOUT,
PROP_STUN_RELIABLE_TIMEOUT,
PROP_NOMINATION_MODE,
};
......@@ -439,6 +441,24 @@ nice_agent_class_init (NiceAgentClass *klass)
0, /* default set in init */
G_PARAM_READWRITE));
/**
* NiceAgent:nomination-mode:
*
* The nomination mode used in the ICE specification for describing
* the selection of valid pairs to be used upstream.
* <para> See also: #NiceNominationMode </para>
*
* Since: UNRELEASED
*/
g_object_class_install_property (gobject_class, PROP_NOMINATION_MODE,
g_param_spec_enum (
"nomination-mode",
"ICE nomination mode",
"Nomination mode used in the ICE specification for describing "
"the selection of valid pairs to be used upstream",
NICE_TYPE_NOMINATION_MODE, NICE_NOMINATION_MODE_AGGRESSIVE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/**
* NiceAgent:proxy-ip:
*
......@@ -1096,6 +1116,7 @@ nice_agent_init (NiceAgent *agent)
agent->stun_server_port = DEFAULT_STUN_PORT;
agent->controlling_mode = TRUE;
agent->max_conn_checks = NICE_AGENT_MAX_CONNECTIVITY_CHECKS_DEFAULT;
agent->nomination_mode = NICE_NOMINATION_MODE_AGGRESSIVE;
agent->discovery_list = NULL;
agent->discovery_unsched_items = 0;
......@@ -1144,6 +1165,23 @@ nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat)
}
NICEAPI_EXPORT NiceAgent *
nice_agent_new_full (GMainContext *ctx,
NiceCompatibility compat,
gboolean reliable,
NiceNominationMode nomination)
{
NiceAgent *agent = g_object_new (NICE_TYPE_AGENT,
"compatibility", compat,
"main-context", ctx,
"reliable", reliable,
"nomination-mode", nomination,
NULL);
return agent;
}
static void
nice_agent_get_property (
GObject *object,
......@@ -1190,6 +1228,10 @@ nice_agent_get_property (
/* XXX: should we prune the list of already existing checks? */
break;
case PROP_NOMINATION_MODE:
g_value_set_enum (value, agent->nomination_mode);
break;
case PROP_PROXY_IP:
g_value_set_string (value, agent->proxy_ip);
break;
......@@ -1394,6 +1436,10 @@ nice_agent_set_property (
agent->max_conn_checks = g_value_get_uint (value);
break;
case PROP_NOMINATION_MODE:
agent->nomination_mode = g_value_get_enum (value);
break;
case PROP_PROXY_IP:
g_free (agent->proxy_ip);
agent->proxy_ip = g_value_dup_string (value);
......@@ -3298,6 +3344,19 @@ static gboolean priv_add_remote_candidate (
username, password, priority);
}
if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
/* note: If there are TCP candidates for a media stream,
* a controlling agent MUST use the regular selection algorithm,
* RFC 6544, sect 8, "Concluding ICE Processing"
*/
if (agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE &&
transport != NICE_CANDIDATE_TRANSPORT_UDP) {
nice_debug ("Agent %p : we have TCP candidates, switching back "
"to regular nomination mode", agent);
agent->nomination_mode = NICE_NOMINATION_MODE_REGULAR;
}
}
if (base_addr)
candidate->base_addr = *base_addr;
......
......@@ -377,6 +377,26 @@ typedef enum
NICE_PROXY_TYPE_LAST = NICE_PROXY_TYPE_HTTP,
} NiceProxyType;
/**
* NiceNominationMode:
* @NICE_NOMINATION_MODE_AGGRESSIVE: Aggressive nomination mode
* @NICE_NOMINATION_MODE_REGULAR: Regular nomination mode
*
* An enum to specity the kind of nomination mode to use by
* the agent, as described in RFC 5245. Two modes exists,
* regular and aggressive. They differ by the way the controlling
* agent chooses to put the USE-CANDIDATE attribute in its STUN
* messages. The aggressive mode is supposed to nominate a pair
* faster, than the regular mode, potentially causing the nominated
* pair to change until the connection check completes.
*
* Since: UNRELEASED
*/
typedef enum
{
NICE_NOMINATION_MODE_REGULAR = 0,
NICE_NOMINATION_MODE_AGGRESSIVE,
} NiceNominationMode;
/**
* NiceAgentRecvFunc:
......@@ -428,6 +448,28 @@ nice_agent_new (GMainContext *ctx, NiceCompatibility compat);
NiceAgent *
nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat);
/**
* nice_agent_new_full:
* @ctx: The Glib Mainloop Context to use for timers
* @compat: The compatibility mode of the agent
* @reliable: The reliability mode of the agent
* @nomination: The nomination mode of the agent
*
* Create a new #NiceAgent with parameters that must be be defined at
* construction time.
* The returned object must be freed with g_object_unref()
* <para> See also: #NiceNominationMode </para>
*
* Since: UNRELEASED
*
* Returns: The new agent GObject
*/
NiceAgent *
nice_agent_new_full (GMainContext *ctx,
NiceCompatibility compat,
gboolean reliable,
NiceNominationMode nomination);
/**
* nice_agent_add_local_address:
* @agent: The #NiceAgent Object
......@@ -447,7 +489,6 @@ nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat);
gboolean
nice_agent_add_local_address (NiceAgent *agent, NiceAddress *addr);
/**
* nice_agent_add_stream:
* @agent: The #NiceAgent Object
......
......@@ -502,7 +502,62 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen
if (s_nominated < stream->n_components &&
s_waiting_for_nomination) {
keep_timer_going = TRUE;
if (agent->controlling_mode) {
if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
if (agent->nomination_mode == NICE_NOMINATION_MODE_REGULAR &&
agent->controlling_mode &&
((waiting == 0 && s_inprogress == 0) ||
(s_succeeded + s_discovered) >= 5 * stream->n_components)){
/* ICE 8.1.1.1 Regular nomination
* we choose to nominate the valid pair if
* there is no pair left waiting or in-progress or
* if there are at least 5 valid pairs per stream on average.
*
* This is the "stopping criterion" described in 8.1.1.1, and is
* a "local optimization" between accumulating more valid pairs,
* and limiting the time spent waiting for in-progress connections
* checks until they finally fail.
*/
GSList *component_item;
for (component_item = stream->components; component_item;
component_item = component_item->next) {
NiceComponent *component = component_item->data;
gboolean already_done = FALSE;
/* verify that the choice of the pair to be nominated
* has not already been done
*/
for (k = stream->conncheck_list; k ; k = k->next) {
CandidateCheckPair *p = k->data;
if (p->component_id == component->id &&
p->use_candidate_on_next_check) {
already_done = TRUE;
break;
}
}
/* choose a pair to be nominated in the list of valid
* pairs, and add it to the triggered checks list
*/
if (!already_done) {
for (k = stream->conncheck_list; k ; k = k->next) {
CandidateCheckPair *p = k->data;
/* note: highest priority item selected (list always sorted) */
if (p->component_id == component->id &&
!p->nominated &&
!p->use_candidate_on_next_check &&
p->valid) {
nice_debug ("Agent %p : restarting check %p with "
"USE-CANDIDATE attrib (regular nomination)", agent, p);
p->use_candidate_on_next_check = TRUE;
priv_add_pair_to_triggered_check_queue (agent, p);
break; /* move to the next component */
}
}
}
}
}
} else if (agent->controlling_mode) {
GSList *component_item;
for (component_item = stream->components; component_item;
......@@ -1571,10 +1626,40 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice
g_assert (component);
if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) &&
agent->controlling_mode)
return;
/* step: search for at least one nominated pair */
for (i = stream->conncheck_list; i; i = i->next) {
CandidateCheckPair *pair = i->data;
if (pair->local == localcand && pair->remote == remotecand) {
if (pair->local == localcand && pair->remote == remotecand &&
NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
/* ICE, 7.2.1.5. Updating the Nominated Flag */
/* note: TCP candidates typically produce peer reflexive
* candidate, generating a "discovered" pair that can be
* nominated.
*/
if (pair->valid) {
nice_debug ("Agent %p : marking pair %p (%s) as nominated",
agent, pair, pair->foundation);
pair->nominated = TRUE;
priv_update_selected_pair (agent, component, pair);
/* Do not step down to CONNECTED if we're already at state READY*/
if (component->state != NICE_COMPONENT_STATE_READY) {
/* 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);
}
priv_update_check_list_state_for_ready (agent, stream, component);
} else if (pair->state == NICE_CHECK_IN_PROGRESS) {
pair->mark_nominated_on_response_arrival = TRUE;
nice_debug ("Agent %p : pair %p (%s) is in-progress, "
"will be nominated on response receipt.",
agent, pair, pair->foundation);
}
} else if (pair->local == localcand && pair->remote == remotecand) {
nice_debug ("Agent %p : marking pair %p (%s) as nominated", agent, pair, pair->foundation);
pair->nominated = TRUE;
if (pair->valid) {
......@@ -2174,7 +2259,35 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair)
pair->prflx_priority, controlling);
}
if (cand_use)
if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
switch (agent->nomination_mode) {
case NICE_NOMINATION_MODE_REGULAR:
{
/* We are doing regular nomination, so we set the use-candidate
* attrib, when the controlling agent decided which valid pair to
* resend with this flag in priv_conn_check_tick_stream()
*/
cand_use = pair->use_candidate_on_next_check;
nice_debug ("Agent %p : %s: set cand_use=%d "
"(regular nomination).", agent, G_STRFUNC, cand_use);
break;
}
case NICE_NOMINATION_MODE_AGGRESSIVE:
{
/* We are doing aggressive nomination, we set the use-candidate
* attrib in every check we send, when we are the controlling
* agent, RFC 5245, 8.1.1.2
*/
cand_use = controlling;
nice_debug ("Agent %p : %s: set cand_use=%d "
"(aggressive nomination).", agent, G_STRFUNC, cand_use);
break;
}
default:
/* Nothing to do. */
break;
}
} else if (cand_use)
pair->nominated = controlling;
if (uname_len > 0) {
......@@ -2781,12 +2894,66 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre
local_candidate, remote_candidate);
}
/* Note: this assignment helps to reduce the numbers of cases
* to be tested. If ok_pair and p refer to distinct pairs, it
* means that ok_pair is a discovered peer reflexive one,
* caused by the check made on pair p. In that case, the
* flags to be tested are on p, but the nominated flag will be
* set on ok_pair. When there's no discovered pair, p and
* ok_pair refer to the same pair.
* To summarize : p is a SUCCEEDED pair, ok_pair is a
* DISCOVERED, VALID, and eventually NOMINATED pair.
*/
if (!ok_pair)
ok_pair = p;
/* step: updating nominated flag (ICE 7.1.2.2.4 "Updating the
Nominated Flag" (ID-19) */
if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
nice_debug ("Agent %p : Updating nominated flag (%s): "
"ok_pair=%p (%d/%d) p=%p (%d/%d) (ucnc/mnora)",
agent, p->local->transport == NICE_CANDIDATE_TRANSPORT_UDP ?
"UDP" : "TCP",
ok_pair, ok_pair->use_candidate_on_next_check,
ok_pair->mark_nominated_on_response_arrival,
p, p->use_candidate_on_next_check,
p->mark_nominated_on_response_arrival);
if (agent->controlling_mode) {
switch (agent->nomination_mode) {
case NICE_NOMINATION_MODE_REGULAR:
if (p->use_candidate_on_next_check) {
nice_debug ("Agent %p : marking pair %p (%s) as nominated "
"(regular nomination, control=1, "
"use_cand_on_next_check=1).",
agent, ok_pair, ok_pair->foundation);
ok_pair->nominated = TRUE;
}
break;
case NICE_NOMINATION_MODE_AGGRESSIVE:
if (!p->nominated) {
nice_debug ("Agent %p : marking pair %p (%s) as nominated "
"(aggressive nomination, control=1).",
agent, ok_pair, ok_pair->foundation);
ok_pair->nominated = TRUE;
}
break;
default:
/* Nothing to do */
break;
}
} else {
if (p->mark_nominated_on_response_arrival) {
nice_debug ("Agent %p : marking pair %p (%s) as nominated "
"(%s nomination, control=0, mark_on_response=1).",
agent, ok_pair, ok_pair->foundation,
agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE ?
"aggressive" : "regular");
ok_pair->nominated = TRUE;
}
}
}
if (ok_pair->nominated == TRUE) {
priv_update_selected_pair (agent, component, ok_pair);
priv_print_conn_check_lists (agent, G_STRFUNC,
......@@ -3668,8 +3835,7 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream,
stun_usage_ice_conncheck_use_candidate (&req);
uint32_t priority = stun_usage_ice_conncheck_priority (&req);
if (agent->controlling_mode ||
agent->compatibility == NICE_COMPATIBILITY_GOOGLE ||
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE ||
agent->compatibility == NICE_COMPATIBILITY_MSN ||
agent->compatibility == NICE_COMPATIBILITY_OC2007)
use_candidate = TRUE;
......
......@@ -87,6 +87,8 @@ struct _CandidateCheckPair
gboolean nominated;
gboolean timer_restarted;
gboolean valid;
gboolean use_candidate_on_next_check;
gboolean mark_nominated_on_response_arrival;
guint64 priority;
guint32 prflx_priority;
GTimeVal next_tick; /* next tick timestamp */
......
......@@ -57,6 +57,7 @@ AC_PROG_CC
AM_PROG_AR
LT_PREREQ([2.2.6])
LT_INIT([dlopen win32-dll disable-static])
AC_PATH_PROG([GLIB_MKENUMS],[glib-mkenums])
# Check Operating System
AC_MSG_CHECKING([operating system])
......
......@@ -5,6 +5,7 @@ NiceAgent
NiceComponentState
NiceComponentType
NiceProxyType
NiceNominationMode
NiceCompatibility
NiceAgentRecvFunc
NiceInputMessage
......@@ -12,6 +13,7 @@ NiceOutputMessage
NICE_AGENT_MAX_REMOTE_CANDIDATES
nice_agent_new
nice_agent_new_reliable
nice_agent_new_full
nice_agent_add_local_address
nice_agent_set_port_range
nice_agent_add_stream
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment