Commit 4a378b88 authored by Matthew Waters's avatar Matthew Waters Committed by Olivier Crête

agent: implement support for RFC7675 - Consent Freshness

Specified in https://tools.ietf.org/html/rfc7675

RFC 7675 is a slight modification of the existing keepalive connection
checks that could be enabled manually or were used with the GOOGLE
compatibility mode.

Slight differences from the existing keepalive connection checks
include:
- an additional consent expiry timer instead of relying on all binding
  requests to succeed.
- 403: 'Forbidden' stun error-code which revokes consent with immediate
  effect.
parent 3492be60
......@@ -61,6 +61,8 @@ ICE
STUN
http://tools.ietf.org/html/rfc3489 (old)
http://tools.ietf.org/html/rfc5389
STUN Consent Freshness RFC
https://tools.ietf.org/html/rfc7675
TURN
http://tools.ietf.org/html/rfc5766
RTP
......
......@@ -106,6 +106,10 @@ nice_input_message_iter_compare (const NiceInputMessageIter *a,
#define NICE_AGENT_TIMER_TA_DEFAULT 20 /* timer Ta, msecs (impl. defined) */
#define NICE_AGENT_TIMER_TR_DEFAULT 25000 /* timer Tr, msecs (impl. defined) */
#define NICE_AGENT_TIMER_CONSENT_DEFAULT 5000 /* msec timer consent freshness connchecks (RFC 7675) */
#define NICE_AGENT_TIMER_CONSENT_TIMEOUT 10000 /* msec timer for consent checks to timeout and assume consent lost (RFC 7675) */
#define NICE_AGENT_TIMER_MIN_CONSENT_INTERVAL 4000 /* msec timer minimum for consent lost requests (RFC 7675) */
#define NICE_AGENT_TIMER_KEEPALIVE_TIMEOUT 50000 /* msec timer for keepalive (without consent checks) to timeout and assume conection lost */
#define NICE_AGENT_MAX_CONNECTIVITY_CHECKS_DEFAULT 100 /* see RFC 8445 6.1.2.5 */
......@@ -184,6 +188,8 @@ struct _NiceAgent
guint conncheck_ongoing_idle_delay; /* ongoing delay before timer stop */
gboolean controlling_mode; /* controlling mode used by the
conncheck */
gboolean consent_freshness; /* rfc 7675 consent freshness with
connchecks */
/* XXX: add pointer to internal data struct for ABI-safe extensions */
};
......
......@@ -123,6 +123,7 @@ enum
PROP_ICE_TRICKLE,
PROP_SUPPORT_RENOMINATION,
PROP_IDLE_TIMEOUT,
PROP_CONSENT_FRESHNESS,
};
......@@ -773,6 +774,8 @@ nice_agent_class_init (NiceAgentClass *klass)
* This is always enabled if the compatibility mode is
* %NICE_COMPATIBILITY_GOOGLE.
*
* This is always enabled if the 'consent-freshness' property is %TRUE
*
* Since: 0.1.8
*/
g_object_class_install_property (gobject_class, PROP_KEEPALIVE_CONNCHECK,
......@@ -890,6 +893,28 @@ nice_agent_class_init (NiceAgentClass *klass)
FALSE,
G_PARAM_READWRITE));
/**
* NiceAgent:consent-freshness
*
* Whether to perform periodic consent freshness checks as specified in
* RFC 7675. When %TRUE, the agent will periodically send binding requests
* to the peer to maintain the consent to send with the peer. On receipt
* of any authenticated error response, a component will immediately move
* to the failed state.
*
* Setting this property to %TRUE implies that 'keepalive-conncheck' should
* be %TRUE as well.
*
* Since: 0.1.20
*/
g_object_class_install_property (gobject_class, PROP_CONSENT_FRESHNESS,
g_param_spec_boolean (
"consent-freshness",
"Consent Freshness",
"Whether to perform the consent freshness checks as specified in RFC 7675",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/* install signals */
/**
......@@ -1314,6 +1339,7 @@ nice_agent_new_full (GMainContext *ctx,
"full-mode", (flags & NICE_AGENT_OPTION_LITE_MODE) ? FALSE : TRUE,
"ice-trickle", (flags & NICE_AGENT_OPTION_ICE_TRICKLE) ? TRUE : FALSE,
"support-renomination", (flags & NICE_AGENT_OPTION_SUPPORT_RENOMINATION) ? TRUE : FALSE,
"consent-freshness", (flags & NICE_AGENT_OPTION_CONSENT_FRESHNESS) ? TRUE : FALSE,
NULL);
return agent;
......@@ -1438,7 +1464,7 @@ nice_agent_get_property (
break;
case PROP_KEEPALIVE_CONNCHECK:
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE)
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE || agent->consent_freshness)
g_value_set_boolean (value, TRUE);
else
g_value_set_boolean (value, agent->keepalive_conncheck);
......@@ -1464,6 +1490,10 @@ nice_agent_get_property (
g_value_set_boolean (value, agent->use_ice_trickle);
break;
case PROP_CONSENT_FRESHNESS:
g_value_set_boolean (value, agent->consent_freshness);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
......@@ -1502,9 +1532,14 @@ nice_agent_init_stun_agent (NiceAgent *agent, StunAgent *stun_agent)
STUN_AGENT_USAGE_USE_FINGERPRINT |
STUN_AGENT_USAGE_NO_ALIGNED_ATTRIBUTES);
} else {
StunAgentUsageFlags stun_usage = 0;
if (agent->consent_freshness)
stun_usage |= STUN_AGENT_USAGE_CONSENT_FRESHNESS;
stun_agent_init (stun_agent, STUN_ALL_KNOWN_ATTRIBUTES,
STUN_COMPATIBILITY_RFC5389,
STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
stun_usage | STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
STUN_AGENT_USAGE_USE_FINGERPRINT);
}
stun_agent_set_software (stun_agent, agent->software_attribute);
......@@ -1677,6 +1712,10 @@ nice_agent_set_property (
agent->use_ice_trickle = g_value_get_boolean (value);
break;
case PROP_CONSENT_FRESHNESS:
agent->consent_freshness = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
......@@ -5187,6 +5226,13 @@ nice_agent_send_messages_nonblocking_internal (
goto done;
}
if (component->selected_pair.local != NULL &&
!component->selected_pair.remote_consent.have) {
g_set_error (&child_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
"Consent to send has been revoked by the peer");
goto done;
}
/* FIXME: Cancellation isn’t yet supported, but it doesn’t matter because
* we only deal with non-blocking writes. */
if (component->selected_pair.local != NULL) {
......@@ -5965,6 +6011,8 @@ nice_agent_set_selected_pair (
NICE_COMPONENT_STATE_READY);
/* step: set the selected pair */
/* XXX: assume we have consent to send to this selected remote address */
pair.remote_consent.have = TRUE;
nice_component_update_selected_pair (agent, component, &pair);
agent_signal_new_selected_pair (agent, stream_id, component_id,
(NiceCandidate *) pair.local, (NiceCandidate *) pair.remote);
......@@ -7103,3 +7151,28 @@ nice_agent_get_sockets (NiceAgent *agent, guint stream_id, guint component_id)
return array;
}
NICEAPI_EXPORT gboolean
nice_agent_consent_lost (
NiceAgent *agent,
guint stream_id,
guint component_id)
{
gboolean result = FALSE;
NiceComponent *component;
agent_lock (agent);
if (!agent->consent_freshness) {
g_warning ("Agent %p: Attempt made to signal consent lost for "
"stream/component %u/%u but RFC7675/consent-freshness is not enabled "
"for this agent. Ignoring request", agent, stream_id, component_id);
} else if (agent_find_component (agent, stream_id, component_id, NULL, &component)) {
nice_debug ("Agent %p: local consent lost for stream/component %u/%u", agent,
component->stream_id, component->id);
component->have_local_consent = FALSE;
result = TRUE;
}
agent_unlock_and_emit (agent);
return result;
}
......@@ -407,6 +407,7 @@ typedef enum
* @NICE_AGENT_OPTION_ICE_TRICKLE: Enable ICE trickle mode
* @NICE_AGENT_OPTION_SUPPORT_RENOMINATION: Enable renomination triggered by NOMINATION STUN attribute
* proposed here: https://tools.ietf.org/html/draft-thatcher-ice-renomination-00
* @NICE_AGENT_OPTION_CONSENT_FRESHNESS: Enable RFC 7675 consent freshness support. (Since: 0.1.20)
*
* These are options that can be passed to nice_agent_new_full(). They set
* various properties on the agent. Not including them sets the property to
......@@ -420,6 +421,7 @@ typedef enum {
NICE_AGENT_OPTION_LITE_MODE = 1 << 2,
NICE_AGENT_OPTION_ICE_TRICKLE = 1 << 3,
NICE_AGENT_OPTION_SUPPORT_RENOMINATION = 1 << 4,
NICE_AGENT_OPTION_CONSENT_FRESHNESS = 1 << 5,
} NiceAgentOption;
/**
......@@ -919,6 +921,9 @@ nice_agent_get_remote_candidates (
* "ICE Restarts"), as well as when reacting (spec section 9.2.1.1.
* "Detecting ICE Restart") to a restart.
*
* If consent-freshness has been enabled on @agent, as specified in RFC7675
* then restarting streams will restore the local consent.
*
* Returns: %TRUE on success %FALSE on error
**/
gboolean
......@@ -938,6 +943,9 @@ nice_agent_restart (
* Unlike nice_agent_restart(), this applies to a single stream. It also
* does not generate a new tie breaker.
*
* If consent-freshness has been enabled on @agent, as specified in RFC7675
* then restart @stream_id will restore the local consent for that stream.
*
* Returns: %TRUE on success %FALSE on error
*
* Since: 0.1.6
......@@ -1659,6 +1667,34 @@ nice_agent_peer_candidate_gathering_done (
NiceAgent *agent,
guint stream_id);
/**
* nice_agent_consent_lost:
* @agent: The #NiceAgent Object
* @stream_id: The ID of the stream
* @component_id: The ID of the component
*
* Notifies the agent that consent to receive has been revoked. This will
* cause the component to fail with 403 'Forbidden' all incoming STUN binding
* requests as specified in RFC 7675.
*
* A stream with a component in the consent-lost state can be reused by
* performing an ice restart with nice_agent_restart() or
* nice_agent_restart_stream().
*
* Calling the function only has an effect when @agent has been created with
* @NICE_AGENT_OPTION_CONSENT_FRESHNESS.
*
* Returns: %FALSE if the stream or component could not be found or consent
* freshness is not enabled, %TRUE otherwise
*
* Since: 0.1.20
*/
gboolean
nice_agent_consent_lost (
NiceAgent *agent,
guint stream_id,
guint component_id);
/**
* nice_agent_close_async:
* @agent: The #NiceAgent object
......
......@@ -307,10 +307,10 @@ nice_component_clean_turn_servers (NiceAgent *agent, NiceComponent *cmp)
static void
nice_component_clear_selected_pair (NiceComponent *component)
{
if (component->selected_pair.keepalive.tick_source != NULL) {
g_source_destroy (component->selected_pair.keepalive.tick_source);
g_source_unref (component->selected_pair.keepalive.tick_source);
component->selected_pair.keepalive.tick_source = NULL;
if (component->selected_pair.remote_consent.tick_source != NULL) {
g_source_destroy (component->selected_pair.remote_consent.tick_source);
g_source_unref (component->selected_pair.remote_consent.tick_source);
component->selected_pair.remote_consent.tick_source = NULL;
}
memset (&component->selected_pair, 0, sizeof(CandidatePair));
......@@ -458,6 +458,8 @@ nice_component_restart (NiceComponent *cmp)
/* Reset the priority to 0 to make sure we get a new pair */
cmp->selected_pair.priority = 0;
cmp->have_local_consent = TRUE;
/* note: component state managed by agent */
}
......@@ -499,6 +501,7 @@ nice_component_update_selected_pair (NiceAgent *agent, NiceComponent *component,
component->selected_pair.remote = pair->remote;
component->selected_pair.priority = pair->priority;
component->selected_pair.stun_priority = pair->stun_priority;
component->selected_pair.remote_consent.have = pair->remote_consent.have;
nice_component_add_valid_candidate (agent, component,
(NiceCandidate *) pair->remote);
......@@ -580,6 +583,7 @@ nice_component_set_selected_remote_candidate (NiceComponent *component,
component->selected_pair.local = (NiceCandidateImpl *) local;
component->selected_pair.remote = (NiceCandidateImpl *) remote;
component->selected_pair.priority = priority;
component->selected_pair.remote_consent.have = TRUE;
/* Get into fallback mode where packets from any source is accepted once
* this has been called. This is the expected behavior of pre-ICE SIP.
......@@ -1107,6 +1111,8 @@ nice_component_init (NiceComponent *component)
g_queue_init (&component->queued_tcp_packets);
g_queue_init (&component->incoming_checks);
component->have_local_consent = TRUE;
}
static void
......
......@@ -65,17 +65,23 @@ G_BEGIN_DECLS
typedef struct _CandidatePair CandidatePair;
typedef struct _CandidatePairKeepalive CandidatePairKeepalive;
typedef struct _CandidatePairConsentCheck CandidatePairConsentCheck;
typedef struct _IncomingCheck IncomingCheck;
struct _CandidatePairKeepalive
{
guint64 next_tick; /* next tick timestamp */
GSource *tick_source;
guint stream_id;
guint component_id;
StunTimer timer;
uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6];
StunMessage stun_message;
};
struct _CandidatePairConsentCheck
{
GSource *tick_source;
gboolean have;
guint64 last_received; /* g_get_monotonic_time() of last remote
consent received */
};
struct _CandidatePair
......@@ -85,6 +91,7 @@ struct _CandidatePair
guint64 priority; /* candidate pair priority */
guint32 stun_priority;
CandidatePairKeepalive keepalive;
CandidatePairConsentCheck remote_consent;
};
struct _IncomingCheck
......@@ -224,6 +231,8 @@ struct _NiceComponent {
* ACKs on. The messages are dequeued to the pseudo-TCP socket once a selected
* UDP socket is available. This is only used for reliable Components. */
GQueue queued_tcp_packets;
gboolean have_local_consent;
};
typedef struct {
......
......@@ -1234,66 +1234,43 @@ static gboolean priv_conn_check_tick_agent_locked (NiceAgent *agent,
return TRUE;
}
static gboolean priv_conn_keepalive_retransmissions_tick_agent_locked (
static gboolean priv_conn_remote_consent_tick_agent_locked (
NiceAgent *agent, gpointer pointer)
{
CandidatePair *pair = (CandidatePair *) pointer;
guint64 consent_timeout = 0;
guint64 now;
g_source_destroy (pair->keepalive.tick_source);
g_source_unref (pair->keepalive.tick_source);
pair->keepalive.tick_source = NULL;
switch (stun_timer_refresh (&pair->keepalive.timer)) {
case STUN_USAGE_TIMER_RETURN_TIMEOUT:
{
/* Time out */
StunTransactionId id;
NiceComponent *component;
if (!agent_find_component (agent,
pair->keepalive.stream_id, pair->keepalive.component_id,
NULL, &component)) {
nice_debug ("Could not find stream or component in"
" priv_conn_keepalive_retransmissions_tick");
return FALSE;
}
stun_message_id (&pair->keepalive.stun_message, id);
stun_agent_forget_transaction (&component->stun_agent, id);
pair->keepalive.stun_message.buffer = NULL;
if (agent->media_after_tick) {
nice_debug ("Agent %p : Keepalive conncheck timed out!! "
"but media was received. Suspecting keepalive lost because of "
"network bottleneck", agent);
} else {
nice_debug ("Agent %p : Keepalive conncheck timed out!! "
"peer probably lost connection", agent);
agent_signal_component_state_change (agent,
pair->keepalive.stream_id, pair->keepalive.component_id,
NICE_COMPONENT_STATE_FAILED);
}
break;
}
case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
/* Retransmit */
agent_socket_send (pair->local->sockptr, &pair->remote->c.addr,
stun_message_length (&pair->keepalive.stun_message),
(gchar *)pair->keepalive.stun_buffer);
if (pair->remote_consent.tick_source) {
g_source_destroy (pair->remote_consent.tick_source);
g_source_unref (pair->remote_consent.tick_source);
}
pair->remote_consent.tick_source = NULL;
nice_debug ("Agent %p : Retransmitting keepalive conncheck",
agent);
if (agent->consent_freshness) {
consent_timeout = NICE_AGENT_TIMER_CONSENT_TIMEOUT * 1000;
} else {
consent_timeout = NICE_AGENT_TIMER_KEEPALIVE_TIMEOUT* 1000;
}
G_GNUC_FALLTHROUGH;
case STUN_USAGE_TIMER_RETURN_SUCCESS:
agent_timeout_add_with_context (agent,
&pair->keepalive.tick_source,
"Pair keepalive", stun_timer_remainder (&pair->keepalive.timer),
priv_conn_keepalive_retransmissions_tick_agent_locked, pair);
break;
default:
g_assert_not_reached();
break;
now = g_get_monotonic_time();
if (now - pair->remote_consent.last_received > consent_timeout) {
guint64 time_since = now - pair->remote_consent.last_received;
pair->remote_consent.have = FALSE;
nice_debug ("Agent %p : pair %p consent for stream/component %u/%u timed "
"out! -> FAILED. Last consent received: %" G_GUINT64_FORMAT ".%" G_GUINT64_FORMAT "s ago",
agent, pair, pair->keepalive.stream_id, pair->keepalive.component_id,
time_since / G_USEC_PER_SEC, time_since % G_USEC_PER_SEC);
agent_signal_component_state_change (agent, pair->keepalive.stream_id,
pair->keepalive.component_id, NICE_COMPONENT_STATE_FAILED);
} else {
guint64 delay = (consent_timeout - now - pair->remote_consent.last_received) / 1000;
nice_debug ("Agent %p : pair %p rechecking consent in %" G_GUINT64_FORMAT ".%03" G_GUINT64_FORMAT "s",
agent, pair, delay / 1000, delay % 1000);
agent_timeout_add_with_context (agent,
&pair->remote_consent.tick_source,
"Pair remote consent", delay,
priv_conn_remote_consent_tick_agent_locked, pair);
}
return FALSE;
......@@ -1400,12 +1377,17 @@ static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent)
guint64 next_timer_tick;
now = g_get_monotonic_time ();
min_next_tick = now + 1000 * NICE_AGENT_TIMER_TR_DEFAULT;
if (agent->consent_freshness) {
min_next_tick = now + 1000 * NICE_AGENT_TIMER_MIN_CONSENT_INTERVAL;
} else {
min_next_tick = now + 1000 * NICE_AGENT_TIMER_TR_DEFAULT;
}
/* case 1: session established and media flowing
* (ref ICE sect 11 "Keepalives" RFC-8445)
* TODO: keepalives should be send only when no packet has been sent
* on that pair in the last Tr seconds, and not unconditionally.
* TODO: without RFC 7675 (consent freshness), keepalives should be sent
* only when no packet has been sent on that pair in the last Tr seconds,
* and not unconditionally.
*/
for (i = agent->streams; i; i = i->next) {
......@@ -1417,7 +1399,7 @@ static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent)
/* Disable keepalive checks on TCP candidates unless explicitly enabled */
if (p->local->c.transport != NICE_CANDIDATE_TRANSPORT_UDP &&
!agent->keepalive_conncheck)
!NICE_AGENT_DO_KEEPALIVE_CONNCHECKS (agent))
continue;
if (p->keepalive.next_tick) {
......@@ -1427,8 +1409,7 @@ static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent)
continue;
}
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE ||
agent->keepalive_conncheck) {
if (NICE_AGENT_DO_KEEPALIVE_CONNCHECKS (agent)) {
uint8_t uname[NICE_STREAM_MAX_UNAME];
size_t uname_len =
priv_create_username (agent, agent_find_stream (agent, stream->id),
......@@ -1438,29 +1419,24 @@ static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent)
size_t password_len = priv_get_password (agent,
agent_find_stream (agent, stream->id),
(NiceCandidate *) p->remote, &password);
uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6];
StunMessage stun_message;
if (p->keepalive.stun_message.buffer != NULL) {
nice_debug ("Agent %p: Keepalive for s%u:c%u still"
" retransmitting, not restarting", agent, stream->id,
component->id);
continue;
}
if (nice_debug_is_enabled ()) {
gchar tmpbuf[INET6_ADDRSTRLEN];
nice_address_to_string (&p->remote->c.addr, tmpbuf);
nice_debug ("Agent %p : Keepalive STUN-CC REQ to '%s:%u', "
"(c-id:%u), username='%.*s' (%" G_GSIZE_FORMAT "), "
"password='%.*s' (%" G_GSIZE_FORMAT "), priority=%08x.",
agent, tmpbuf, nice_address_get_port (&p->remote->c.addr),
component->id, (int) uname_len, uname, uname_len,
(int) password_len, password, password_len,
p->stun_priority);
}
if (uname_len > 0) {
if (nice_debug_is_enabled ()) {
gchar tmpbuf[INET6_ADDRSTRLEN];
nice_address_to_string (&p->remote->c.addr, tmpbuf);
nice_debug ("Agent %p : Keepalive STUN-CC REQ to '%s:%u', "
"(c-id:%u), username='%.*s' (%" G_GSIZE_FORMAT "), "
"password='%.*s' (%" G_GSIZE_FORMAT "), priority=%08x.",
agent, tmpbuf, nice_address_get_port (&p->remote->c.addr),
component->id, (int) uname_len, uname, uname_len,
(int) password_len, password, password_len,
p->stun_priority);
}
buf_len = stun_usage_ice_conncheck_create (&component->stun_agent,
&p->keepalive.stun_message, p->keepalive.stun_buffer,
sizeof(p->keepalive.stun_buffer),
&stun_message, stun_buffer, sizeof(stun_buffer),
uname, uname_len, password, password_len,
agent->controlling_mode, agent->controlling_mode,
p->stun_priority,
......@@ -1469,27 +1445,31 @@ static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent)
agent_to_ice_compatibility (agent));
nice_debug ("Agent %p: conncheck created %zd - %p",
agent, buf_len, p->keepalive.stun_message.buffer);
agent, buf_len, stun_message.buffer);
if (buf_len > 0) {
stun_timer_start (&p->keepalive.timer,
agent->stun_initial_timeout,
agent->stun_max_retransmissions);
/* random range over 0.8 -> 1.2 as specified in RFC7675 */
double modifier = g_random_double() * 0.4 + 0.8;
guint64 delay = 1000 * MAX((guint64) ((NICE_AGENT_TIMER_CONSENT_DEFAULT) * modifier),
NICE_AGENT_TIMER_MIN_CONSENT_INTERVAL);
p->keepalive.stream_id = stream->id;
p->keepalive.component_id = component->id;
p->keepalive.next_tick = now + delay;
if (p->remote_consent.have) {
if (p->remote_consent.last_received == 0) {
p->remote_consent.last_received = g_get_monotonic_time();
}
priv_conn_remote_consent_tick_agent_locked (agent, p);
}
agent->media_after_tick = FALSE;
/* send the conncheck */
agent_socket_send (p->local->sockptr, &p->remote->c.addr,
buf_len, (gchar *)p->keepalive.stun_buffer);
p->keepalive.stream_id = stream->id;
p->keepalive.component_id = component->id;
p->keepalive.next_tick = now + 1000 * NICE_AGENT_TIMER_TR_DEFAULT;
agent_timeout_add_with_context (agent,
&p->keepalive.tick_source, "Pair keepalive",
stun_timer_remainder (&p->keepalive.timer),
priv_conn_keepalive_retransmissions_tick_agent_locked, p);
buf_len, (gchar *) stun_buffer);
next_timer_tick = now + agent->timer_ta * 1000;
goto done;
......@@ -1498,18 +1478,20 @@ static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent)
}
}
} else {
uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6];
StunMessage stun_message;
buf_len = stun_usage_bind_keepalive (&component->stun_agent,
&p->keepalive.stun_message, p->keepalive.stun_buffer,
sizeof(p->keepalive.stun_buffer));
&stun_message, stun_buffer, sizeof(stun_buffer));
if (buf_len > 0) {
agent_socket_send (p->local->sockptr, &p->remote->c.addr, buf_len,
(gchar *)p->keepalive.stun_buffer);
(gchar *) stun_buffer);
p->keepalive.next_tick = now + 1000 * NICE_AGENT_TIMER_TR_DEFAULT;
if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) {
ms_ice2_legacy_conncheck_send (&p->keepalive.stun_message,
ms_ice2_legacy_conncheck_send (&stun_message,
p->local->sockptr, &p->remote->c.addr);
}
......@@ -2082,6 +2064,7 @@ conn_check_update_selected_pair (NiceAgent *agent, NiceComponent *component,
cpair.remote = (NiceCandidateImpl *) pair->remote;
cpair.priority = pair->priority;
cpair.stun_priority = pair->stun_priority;
cpair.remote_consent.have = TRUE;
nice_component_update_selected_pair (agent, component, &cpair);
......@@ -4291,27 +4274,13 @@ static gboolean priv_map_reply_to_relay_remove (NiceAgent *agent,
static gboolean priv_map_reply_to_keepalive_conncheck (NiceAgent *agent,
NiceComponent *component, StunMessage *resp)
{
StunTransactionId conncheck_id;
StunTransactionId response_id;
stun_message_id (resp, response_id);
if (component->selected_pair.keepalive.stun_message.buffer) {
stun_message_id (&component->selected_pair.keepalive.stun_message,
conncheck_id);
if (memcmp (conncheck_id, response_id, sizeof(StunTransactionId)) == 0) {
nice_debug ("Agent %p : Keepalive for selected pair received.",
agent);
if (component->selected_pair.keepalive.tick_source) {
g_source_destroy (component->selected_pair.keepalive.tick_source);
g_source_unref (component->selected_pair.keepalive.tick_source);
component->selected_pair.keepalive.tick_source = NULL;
}
component->selected_pair.keepalive.stun_message.buffer = NULL;
return TRUE;
}
nice_debug ("Agent %p : Keepalive for selected pair %p received.",
agent, &component->selected_pair);
if (agent->consent_freshness) {
guint64 now = g_get_monotonic_time();
component->selected_pair.remote_consent.last_received = now;
}
return FALSE;
return TRUE;
}
......@@ -4602,6 +4571,41 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream,
return TRUE;
}
if (valid == STUN_VALIDATION_FORBIDDEN) {
CandidatePair *pair = &component->selected_pair;
gchar tmpbuf[INET6_ADDRSTRLEN];
nice_address_to_string (from, tmpbuf);
nice_debug ("Agent %p : received 403: 'Forbidden' for %u/%u (stream/component) from [%s]:%u",
agent, stream->id, component->id, tmpbuf, nice_address_get_port (from));
for (i = stream->conncheck_list; i; i = i->next) {
CandidateCheckPair *p = i->data;
if (nice_address_equal (from, &p->remote->addr)) {
candidate_check_pair_fail (stream, agent, p);
}
}
/* if the pair was selected, it is no longer useful */
if (nice_address_equal (from, &pair->remote->c.addr)) {
pair->remote_consent.have = FALSE;
nice_debug ("Agent %p : pair %p lost consent for %u/%u (stream/component)",
agent, pair, stream->id, component->id);
/* explicit revocation received, we don't need to time out anymore */
if (pair->remote_consent.tick_source) {
g_source_destroy (pair->remote_consent.tick_source);
g_source_unref (pair->remote_consent.tick_source);
pair->remote_consent.tick_source = NULL;
}
agent_signal_component_state_change (agent, stream->id, component->id,
NICE_COMPONENT_STATE_FAILED);
}
return TRUE;
}
username = (uint8_t *) stun_message_find (&req, STUN_ATTRIBUTE_USERNAME,
&username_len);
......@@ -4733,6 +4737,22 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream,
}
}
if (!component->have_local_consent) {
/* RFC 7675: return forbidden to all authenticated requests if we should
* signal lost consent */
nice_debug("Agent %p : returning FORBIDDEN on stream/component %u/%u "
"for lost local consent", agent, stream->id, component->id);
if (stun_agent_init_error (&component->stun_agent, &msg, rbuf, rbuf_len,
&req, STUN_ERROR_FORBIDDEN)) {
rbuf_len = stun_agent_finish_message (&component->stun_agent, &msg, NULL, 0);
if (rbuf_len > 0 && agent->compatibility != NICE_COMPATIBILITY_MSN &&
agent->compatibility != NICE_COMPATIBILITY_OC2007) {
agent_socket_send (nicesock, from, rbuf_len, (const gchar*) rbuf);
}
return TRUE;
}
}
rbuf_len = sizeof (rbuf);
res = stun_usage_ice_conncheck_create_reply (&component->stun_agent, &req,
&msg, rbuf, &rbuf_len, &sockaddr.storage, sizeof (sockaddr),
......
......@@ -49,6 +49,11 @@
#define NICE_CANDIDATE_PAIR_MAX_FOUNDATION NICE_CANDIDATE_MAX_FOUNDATION*2
/* A helper macro to test whether connection checks should continue to be
* performed after a component has successfully connected */
#define NICE_AGENT_DO_KEEPALIVE_CONNCHECKS(obj) \
((obj)->consent_freshness || (obj)->keepalive_conncheck || (obj)->compatibility == NICE_COMPATIBILITY_GOOGLE)
/**
* NiceCheckState:
* @NICE_CHECK_WAITING: Waiting to be scheduled.
......
......@@ -121,6 +121,10 @@
<title>Index of new symbols in 0.1.18</title>
<xi:include href="xml/api-index-0.1.18.xml"><xi:fallback/></xi:include>
</index>
<index role="0.1.20">
<title>Index of new symbols in 0.1.20</title>
<xi:include href="xml/api-index-0.1.20.xml"><xi:fallback/></xi:include>
</index>
<xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
</part>
</book>
......@@ -57,6 +57,7 @@ nice_agent_get_selected_socket
nice_agent_get_sockets
nice_agent_get_component_state
nice_agent_close_async
nice_agent_consent_lost
nice_component_state_to_string
<SUBSECTION Standard>
NICE_AGENT
......
......@@ -18,6 +18,7 @@ nice_address_to_string
nice_agent_add_local_address
nice_agent_add_stream
nice_agent_close_async
nice_agent_consent_lost
nice_agent_recv
nice_agent_recv_messages
nice_agent_recv_nonblocking
......
......@@ -337,6 +337,13 @@ StunValidationStatus stun_agent_validate (StunAgent *agent, StunMessage *msg,
}
}
if (agent->usage_flags & STUN_AGENT_USAGE_CONSENT_FRESHNESS &&
stun_message_get_class (msg) == STUN_ERROR) {
stun_message_find_error (msg, &error_code);
if (error_code == STUN_ERROR_FORBIDDEN) {
return STUN_VALIDATION_FORBIDDEN;
}
}
if (sent_id_idx != -1 && sent_id_idx < STUN_AGENT_MAX_SAVED_IDS) {
agent->sent_ids[sent_id_idx].valid = FALSE;
......
......@@ -123,6 +123,9 @@ typedef enum {
* @STUN_VALIDATION_UNKNOWN_ATTRIBUTE: The message is valid but contains one
* or more unknown comprehension attributes. This is a response, or error,
* or indication message and no error response should be sent
* @STUN_VALIDATION_FORBIDDEN: The message response is valid and indicates
* the peer responded with the error code 403 'Forbidden'. No response
* should be sent.
*
* This enum is used as the return value of stun_agent_validate() and represents
* the status result of the validation of a STUN message.
......@@ -137,6 +140,7 @@ typedef enum {
STUN_VALIDATION_UNMATCHED_RESPONSE,
STUN_VALIDATION_UNKNOWN_REQUEST_ATTRIBUTE,
STUN_VALIDATION_UNKNOWN_ATTRIBUTE,
STUN_VALIDATION_FORBIDDEN,
} StunValidationStatus;
/**
......@@ -168,6 +172,9 @@ typedef enum {
* @STUN_AGENT_USAGE_NO_ALIGNED_ATTRIBUTES: The agent should not assume STUN
* attributes are aligned on 32-bit boundaries when parsing messages and also
* do not add padding when creating messages.
* @STUN_AGENT_USAGE_CONSENT_FRESHNESS: The agent should expect and use
* the %STUN_VALIDATION_FORBIDDEN return value from ERROR-CODE responses and
* abort all transactions accordingly.
*
* This enum defines a bitflag usages for a #StunAgent and they will define how
* the agent should behave, independently of the compatibility mode it uses.
......@@ -183,6 +190,7 @@ typedef enum {
STUN_AGENT_USAGE_NO_INDICATION_AUTH = (1 << 5),
STUN_AGENT_USAGE_FORCE_VALIDATER = (1 << 6),
STUN_AGENT_USAGE_NO_ALIGNED_ATTRIBUTES = (1 << 7),
STUN_AGENT_USAGE_CONSENT_FRESHNESS = (1 << 8),
} StunAgentUsageFlags;
......
......@@ -414,6 +414,8 @@ typedef uint8_t StunTransactionId[STUN_MESSAGE_TRANS_ID_LEN];
* "Bad Request" error as defined in RFC5389
* @STUN_ERROR_UNAUTHORIZED: The ERROR-CODE value for the
* "Unauthorized" error as defined in RFC5389
* @STUN_ERROR_FORBIDDEN: The ERROR-CODE value for the
* "Forbidden" error as defined in RFC7675
* @STUN_ERROR_UNKNOWN_ATTRIBUTE: The ERROR-CODE value for the
* "Unknown Attribute" error as defined in RFC5389
* @STUN_ERROR_ALLOCATION_MISMATCH:The ERROR-CODE value for the
......@@ -457,6 +459,7 @@ typedef enum
STUN_ERROR_TRY_ALTERNATE=300, /* RFC5389 */
STUN_ERROR_BAD_REQUEST=400, /* RFC5389 */
STUN_ERROR_UNAUTHORIZED=401, /* RFC5389 */
STUN_ERROR_FORBIDDEN=403, /* RFC7675 */
STUN_ERROR_UNKNOWN_ATTRIBUTE=420, /* RFC5389 */
STUN_ERROR_ALLOCATION_MISMATCH=437, /* TURN-12 */
STUN_ERROR_STALE_NONCE=438, /* RFC5389 */
......
......@@ -28,7 +28,8 @@ nice_tests = [
'test-drop-invalid',
'test-nomination',
'test-interfaces',
'test-set-port-range'
'test-set-port-range',
'test-consent',
]
if cc.has_header('arpa/inet.h')
......
/*
* This file is part of the Nice GLib ICE library.
*
* (C) 2007 Nokia Corporation. All rights reserved.
* Contact: Kai Vehmanen
* (C) 2020 Matthew Waters <matthew@centricular.com>
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Nice GLib ICE library.
*
* The Initial Developers of the Original Code are Collabora Ltd and Nokia
* Corporation. All Rights Reserved.
*
* Contributors:
* Kai Vehmanen, Nokia
* Matthew Waters, Centricular
*
* Alternatively, the contents of this file may be used under the terms of the
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
* case the provisions of LGPL are applicable instead of those above. If you
* wish to allow use of your version of this file only under the terms of the
* LGPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replace
* them with the notice and other provisions required by the LGPL. If you do
* 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" /* for testing purposes */
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <io.h>
#endif
static NiceComponentState global_lagent_state = NICE_COMPONENT_STATE_LAST;
static NiceComponentState global_ragent_state = NICE_COMPONENT_STATE_LAST;
static guint global_components_ready = 0;
static guint global_components_ready_exit = 0;
static guint global_components_failed = 0;
static guint global_components_failed_exit = 0;
static GMainLoop *global_mainloop = NULL;
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 gint global_ragent_read_exit = 0;
static void priv_print_global_status (void)
{
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 ("test-consent:%s: %p", G_STRFUNC, pointer);
/* signal status via a global variable */
/* note: should not be reached, abort */
g_debug ("ERROR: test has got stuck, aborting...");
exit (-1);
}
static void cb_nice_recv (NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer user_data)
{
g_debug ("test-consent:%s: %p", G_STRFUNC, user_data);
/* XXX: dear compiler, these are for you: */
(void)agent; (void)stream_id; (void)component_id; (void)buf;
if ((intptr_t)user_data == 2) {
global_ragent_read += len;
g_debug ("test-consent: read %u/%u", global_ragent_read, global_ragent_read_exit);
if (global_ragent_read == global_ragent_read_exit) {
g_debug ("test-consent: quit mainloop: read enough data");
g_main_loop_quit (global_mainloop);
}
}
}
static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer data)
{
g_debug ("test-consent:%s: %p", G_STRFUNC, data);
if ((intptr_t)data == 1)
global_lagent_gathering_done = TRUE;
else if ((intptr_t)data == 2)
global_ragent_gathering_done = TRUE;
if (global_lagent_gathering_done &&
global_ragent_gathering_done) {
g_debug ("test-consent: quit mainloop: gathering completed");
g_main_loop_quit (global_mainloop);
}
/* XXX: dear compiler, these are for you: */
(void)agent;
}
static void cb_component_state_changed (NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer data)
{
g_debug ("test-consent:%s: %p", G_STRFUNC, data);
if ((intptr_t)data == 1)
global_lagent_state = state;
else if ((intptr_t)data == 2)
global_ragent_state = state;
if (state == NICE_COMPONENT_STATE_READY)
global_components_ready++;
if (state == NICE_COMPONENT_STATE_FAILED)
global_components_failed++;
g_debug ("test-consent: READY %u/%u FAILED %u/%u.", global_components_ready, global_components_ready_exit, global_components_failed, global_components_failed_exit);
/* signal status via a global variable */
if (global_components_ready == global_components_ready_exit) {
g_debug ("test-consent: quit mainloop: components ready");
g_main_loop_quit (global_mainloop);
return;
}
/* signal status via a global variable */
if (global_components_failed == global_components_failed_exit) {
g_debug ("test-consent: quit mainloop: components failed");
g_main_loop_quit (global_mainloop);
return;
}
/* XXX: dear compiler, these are for you: */
(void)agent; (void)stream_id; (void)data; (void)component_id;
}
static void cb_new_selected_pair(NiceAgent *agent, guint stream_id, guint component_id,
gchar *lfoundation, gchar* rfoundation, gpointer data)
{
g_debug ("test-consent:%s: %p", G_STRFUNC, data);
if ((intptr_t)data == 1)
++global_lagent_cands;
else if ((intptr_t)data == 2)
++global_ragent_cands;
/* XXX: dear compiler, these are for you: */
(void)agent; (void)stream_id; (void)component_id; (void)lfoundation; (void)rfoundation;
}
static void cb_new_candidate(NiceAgent *agent, guint stream_id, guint component_id,
gchar *foundation, gpointer data)
{
g_debug ("test-consent:%s: %p", G_STRFUNC, data);
/* XXX: dear compiler, these are for you: */
(void)agent; (void)stream_id; (void)data; (void)component_id; (void)foundation;
}
static void cb_initial_binding_request_received(NiceAgent *agent, guint stream_id, gpointer data)
{
g_debug ("test-consent:%s: %p", G_STRFUNC, data);
if ((intptr_t)data == 1)
global_lagent_ibr_received = TRUE;
else if ((intptr_t)data == 2)
global_ragent_ibr_received = TRUE;
/* XXX: dear compiler, these are for you: */
(void)agent; (void)stream_id; (void)data;
}
static void priv_get_local_addr (NiceAgent *agent, guint stream_id, guint component_id, NiceAddress *dstaddr)
{
GSList *cands, *i;
cands = nice_agent_get_local_candidates(agent, stream_id, component_id);
for (i = cands; i; i = i->next) {
NiceCandidate *cand = i->data;
if (cand) {
g_assert (dstaddr);
*dstaddr = cand->addr;
}
}
for (i = cands; i; i = i->next)
nice_candidate_free ((NiceCandidate *) i->data);
g_slist_free (cands);
}
static int run_consent_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *baseaddr)
{
NiceAddress laddr, raddr, laddr_rtcp, raddr_rtcp;
NiceCandidate cdes;
GSList *cands;
guint ls_id, rs_id;
/* XXX: dear compiler, these are for you: */
(void)baseaddr;
memset (&cdes, 0, sizeof(NiceCandidate));
cdes.priority = 10000;
strcpy (cdes.foundation, "1");
cdes.type = NICE_CANDIDATE_TYPE_HOST;
cdes.transport = NICE_CANDIDATE_TRANSPORT_UDP;
/* step: initialize variables modified by the callbacks */
global_components_ready = 0;
global_components_ready_exit = 4;
global_components_failed = 0;
global_components_failed_exit = 4;
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;
global_ragent_read_exit = 32;
g_object_set (G_OBJECT (lagent), "controlling-mode", TRUE,
"consent-freshness", TRUE, NULL);
g_object_set (G_OBJECT (ragent), "controlling-mode", FALSE,
"consent-freshness", TRUE, NULL);
/* step: add one stream, with RTP+RTCP components, to each agent */
ls_id = nice_agent_add_stream (lagent, 2);
rs_id = nice_agent_add_stream (ragent, 2);
g_assert_cmpuint (ls_id, >, 0);
g_assert_cmpuint (rs_id, >, 0);
nice_agent_gather_candidates (lagent, ls_id);
nice_agent_gather_candidates (ragent, rs_id);
/* step: attach to mainloop (needed to register the fds) */
nice_agent_attach_recv (lagent, ls_id, NICE_COMPONENT_TYPE_RTP,
g_main_loop_get_context (global_mainloop), cb_nice_recv, (gpointer)1);
nice_agent_attach_recv (lagent, ls_id, NICE_COMPONENT_TYPE_RTCP,
g_main_loop_get_context (global_mainloop), cb_nice_recv, (gpointer)1);
nice_agent_attach_recv (ragent, rs_id, NICE_COMPONENT_TYPE_RTP,
g_main_loop_get_context (global_mainloop), cb_nice_recv, (gpointer)2);
nice_agent_attach_recv (ragent, rs_id, NICE_COMPONENT_TYPE_RTCP,
g_main_loop_get_context (global_mainloop), cb_nice_recv, (gpointer)2);
/* 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-consent: 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 */
priv_get_local_addr (ragent, rs_id, NICE_COMPONENT_TYPE_RTP, &raddr);
g_debug ("test-consent: local RTP port R %u",
nice_address_get_port (&raddr));
priv_get_local_addr (lagent, ls_id, NICE_COMPONENT_TYPE_RTP, &laddr);
g_debug ("test-consent: local RTP port L %u",
nice_address_get_port (&laddr));
priv_get_local_addr (ragent, rs_id, NICE_COMPONENT_TYPE_RTCP, &raddr_rtcp);
g_debug ("test-consent: local RTCP port R %u",
nice_address_get_port (&raddr_rtcp));
priv_get_local_addr (lagent, ls_id, NICE_COMPONENT_TYPE_RTCP, &laddr_rtcp);
g_debug ("test-consent: local RTCP port L %u",
nice_address_get_port (&laddr_rtcp));
/* step: pass the remote candidates to agents */
cands = g_slist_append (NULL, &cdes);
{
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);
g_free (ufrag);
g_free (password);
nice_agent_get_local_credentials(ragent, rs_id, &ufrag, &password);
nice_agent_set_remote_credentials (lagent,
ls_id, ufrag, password);
g_free (ufrag);
g_free (password);
}
cdes.component_id = NICE_COMPONENT_TYPE_RTP;
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);
cdes.component_id = NICE_COMPONENT_TYPE_RTCP;
cdes.addr = raddr_rtcp;
nice_agent_set_remote_candidates (lagent, ls_id, NICE_COMPONENT_TYPE_RTCP, cands);
cdes.addr = laddr_rtcp;
nice_agent_set_remote_candidates (ragent, rs_id, NICE_COMPONENT_TYPE_RTCP, cands);
g_debug ("test-consent: 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 STUN binding requests were sent */
g_assert (global_lagent_ibr_received == TRUE);
g_assert (global_ragent_ibr_received == TRUE);
/* note: verify that correct number of local candidates were reported */
g_assert_cmpint (global_lagent_cands, ==, 2);
g_assert_cmpint (global_ragent_cands, ==, 2);
/* note: verify that agents are in correct state */
g_assert_cmpint (global_lagent_state, ==, NICE_COMPONENT_STATE_READY);
g_assert_cmpint (global_ragent_state, ==, NICE_COMPONENT_STATE_READY);
/* step: send a new test packet from L ot R */
global_ragent_read = 0;
g_assert_cmpint (nice_agent_send (lagent, ls_id, 1, 16, "1234567812345678"), ==, 16);
global_components_ready = 0;
global_components_failed = 0;
/* step: synthesize marking the components as consent failed */
g_assert (nice_agent_consent_lost (ragent, rs_id, NICE_COMPONENT_TYPE_RTP));
g_assert (nice_agent_consent_lost (ragent, rs_id, NICE_COMPONENT_TYPE_RTCP));
/* step: synthesize marking the components as consent failed */
g_assert (nice_agent_consent_lost (lagent, rs_id, NICE_COMPONENT_TYPE_RTP));
g_assert (nice_agent_consent_lost (lagent, rs_id, NICE_COMPONENT_TYPE_RTCP));
/* transition to failed will take roughly 4-6 seconds as that's the pacing
* of the consent connection checks */
g_debug ("test-consent: run loop: consent lost: waiting for components to transition to failed...");
g_main_loop_run (global_mainloop);
/* note: verify that ragent is in correct state after consent lost */
g_assert_cmpint (global_ragent_state, ==, NICE_COMPONENT_STATE_FAILED);
/* note: verify that lagent is in correct state after consent lost */
g_assert_cmpint (global_lagent_state, ==, NICE_COMPONENT_STATE_FAILED);
/* note: verify that all 4 components failed */
g_assert_cmpint (global_components_failed, ==, 4);
/* send another packet after consent lost before ice restart that will fail */
g_assert_cmpint (nice_agent_send (lagent, ls_id, 1, 16, "1234567812345678"), ==, -1);
g_debug ("test-consent: ICE restart...");
/* restart the agent to gather new credentials and clear the consent-lost */
nice_agent_restart (ragent);
nice_agent_restart (lagent);
{
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);
g_free (ufrag);
g_free (password);
nice_agent_get_local_credentials(ragent, rs_id, &ufrag, &password);
nice_agent_set_remote_credentials (lagent,
ls_id, ufrag, password);
g_free (ufrag);
g_free (password);
}
/* step: reset state variables */
global_lagent_ibr_received = FALSE;
global_ragent_ibr_received = FALSE;
global_components_ready = 0;
global_components_failed = 0;
/* step: exchange remote candidates */
cdes.component_id = NICE_COMPONENT_TYPE_RTP;
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);
cdes.component_id = NICE_COMPONENT_TYPE_RTCP;
cdes.addr = raddr_rtcp;
nice_agent_set_remote_candidates (lagent, ls_id, NICE_COMPONENT_TYPE_RTCP, cands);
cdes.addr = laddr_rtcp;
nice_agent_set_remote_candidates (ragent, rs_id, NICE_COMPONENT_TYPE_RTCP, cands);
g_debug ("test-consent: run main loop after ICE restart...");
g_main_loop_run (global_mainloop);
/* note: verify binding requests were resent after restart */
g_assert (global_lagent_ibr_received == TRUE);
g_assert (global_ragent_ibr_received == TRUE);
/* send another packet after consent lost and after ice restart that will succeed */
g_assert_cmpint (nice_agent_send (lagent, ls_id, 1, 16, "1234567812345678"), ==, 16);
global_components_ready = 0;
global_components_failed = 0;
g_debug ("test-consent: run main loop to send packet after ICE restart ...");
g_main_loop_run (global_mainloop);
/* note: verify that payload was succesfully received */
g_assert_cmpint (global_ragent_read, ==, 32);
g_debug ("test-consent: Ran mainloop, removing streams...");
/* step: clean up resources and exit */
g_slist_free (cands);
nice_agent_remove_stream (lagent, ls_id);
nice_agent_remove_stream (ragent, rs_id);
return 0;
}
int main (void)
{
NiceAgent *lagent, *ragent; /* agent's L and R */
NiceAddress baseaddr;
int result;
guint timer_id;
const char *stun_server = NULL, *stun_server_port = NULL;
#ifdef G_OS_WIN32
WSADATA w;
WSAStartup(0x0202, &w);
#endif
global_mainloop = g_main_loop_new (NULL, FALSE);
/* Note: impl limits ...
* - no multi-stream support
* - no IPv6 support
*/
/* step: create the agents L and R */
lagent = nice_agent_new_full (g_main_loop_get_context (global_mainloop), NICE_COMPATIBILITY_RFC5245, NICE_AGENT_OPTION_CONSENT_FRESHNESS);
ragent = nice_agent_new_full (g_main_loop_get_context (global_mainloop), NICE_COMPATIBILITY_RFC5245, NICE_AGENT_OPTION_CONSENT_FRESHNESS);
g_object_set (G_OBJECT (lagent), "ice-tcp", FALSE, NULL);
g_object_set (G_OBJECT (ragent), "ice-tcp", FALSE, NULL);
g_object_set (G_OBJECT (lagent), "upnp", FALSE, NULL);
g_object_set (G_OBJECT (ragent), "upnp", FALSE, NULL);
/* step: add a timer to catch state changes triggered by signals */
timer_id = g_timeout_add (30000, timer_cb, NULL);
/* step: specify which local interface to use */
if (!nice_address_set_from_string (&baseaddr, "127.0.0.1"))
g_assert_not_reached ();
nice_agent_add_local_address (lagent, &baseaddr);
nice_agent_add_local_address (ragent, &baseaddr);
g_signal_connect (G_OBJECT (lagent), "candidate-gathering-done",
G_CALLBACK (cb_candidate_gathering_done), (gpointer)1);
g_signal_connect (G_OBJECT (ragent), "candidate-gathering-done",
G_CALLBACK (cb_candidate_gathering_done), (gpointer)2);
g_signal_connect (G_OBJECT (lagent), "component-state-changed",
G_CALLBACK (cb_component_state_changed), (gpointer)1);
g_signal_connect (G_OBJECT (ragent), "component-state-changed",
G_CALLBACK (cb_component_state_changed), (gpointer)2);
g_signal_connect (G_OBJECT (lagent), "new-selected-pair",
G_CALLBACK (cb_new_selected_pair), (gpointer)1);
g_signal_connect (G_OBJECT (ragent), "new-selected-pair",
G_CALLBACK (cb_new_selected_pair), (gpointer)2);
g_signal_connect (G_OBJECT (lagent), "new-candidate",
G_CALLBACK (cb_new_candidate), (gpointer)1);
g_signal_connect (G_OBJECT (ragent), "new-candidate",
G_CALLBACK (cb_new_candidate), (gpointer)2);
g_signal_connect (G_OBJECT (lagent), "initial-binding-request-received",
G_CALLBACK (cb_initial_binding_request_received), (gpointer)1);
g_signal_connect (G_OBJECT (ragent), "initial-binding-request-received",
G_CALLBACK (cb_initial_binding_request_received), (gpointer)2);
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);
}
/* step: run test the first time */
g_debug ("test-consent: TEST STARTS / consent test");
result = run_consent_test (lagent, ragent, &baseaddr);
priv_print_global_status ();
g_assert_cmpint (result, ==, 0);
g_assert_cmpint (global_lagent_state, ==, NICE_COMPONENT_STATE_READY);
g_assert_cmpint (global_ragent_state, ==, NICE_COMPONENT_STATE_READY);
g_object_unref (lagent);
g_object_unref (ragent);
g_main_loop_unref (global_mainloop);
global_mainloop = NULL;
g_source_remove (timer_id);
#ifdef G_OS_WIN32
WSACleanup();
#endif
return result;
}
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