summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README2
-rw-r--r--agent/agent-priv.h6
-rw-r--r--agent/agent.c77
-rw-r--r--agent/agent.h36
-rw-r--r--agent/component.c14
-rw-r--r--agent/component.h15
-rw-r--r--agent/conncheck.c256
-rw-r--r--agent/conncheck.h5
-rw-r--r--docs/reference/libnice/libnice-docs.xml4
-rw-r--r--docs/reference/libnice/libnice-sections.txt1
-rw-r--r--nice/libnice.sym1
-rw-r--r--stun/stunagent.c7
-rw-r--r--stun/stunagent.h8
-rw-r--r--stun/stunmessage.h3
-rw-r--r--tests/meson.build3
-rw-r--r--tests/test-consent.c520
16 files changed, 830 insertions, 128 deletions
diff --git a/README b/README
index b307b9b..c1a5deb 100644
--- a/README
+++ b/README
@@ -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
diff --git a/agent/agent-priv.h b/agent/agent-priv.h
index 8c08b28..1e9387c 100644
--- a/agent/agent-priv.h
+++ b/agent/agent-priv.h
@@ -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 */
};
diff --git a/agent/agent.c b/agent/agent.c
index 50ea756..2145755 100644
--- a/agent/agent.c
+++ b/agent/agent.c
@@ -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;
+}
diff --git a/agent/agent.h b/agent/agent.h
index 1164138..980b760 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -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
@@ -1660,6 +1668,34 @@ nice_agent_peer_candidate_gathering_done (
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
* @callback: (nullable): A callback that will be called when the closing is
diff --git a/agent/component.c b/agent/component.c
index 13c4e4b..5e86b15 100644
--- a/agent/component.c
+++ b/agent/component.c
@@ -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
diff --git a/agent/component.h b/agent/component.h
index 2e6c496..788e539 100644
--- a/agent/component.h
+++ b/agent/component.h
@@ -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 {
diff --git a/agent/conncheck.c b/agent/conncheck.c
index 680787d..522c980 100644
--- a/agent/conncheck.c
+++ b/agent/conncheck.c
@@ -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),
diff --git a/agent/conncheck.h b/agent/conncheck.h
index 50fa0b1..358ad28 100644
--- a/agent/conncheck.h
+++ b/agent/conncheck.h
@@ -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.
diff --git a/docs/reference/libnice/libnice-docs.xml b/docs/reference/libnice/libnice-docs.xml
index bcca5f4..c4741dc 100644
--- a/docs/reference/libnice/libnice-docs.xml
+++ b/docs/reference/libnice/libnice-docs.xml
@@ -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>
diff --git a/docs/reference/libnice/libnice-sections.txt b/docs/reference/libnice/libnice-sections.txt
index f5c13d7..5c13bce 100644
--- a/docs/reference/libnice/libnice-sections.txt
+++ b/docs/reference/libnice/libnice-sections.txt
@@ -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
diff --git a/nice/libnice.sym b/nice/libnice.sym
index 007be55..1362691 100644
--- a/nice/libnice.sym
+++ b/nice/libnice.sym
@@ -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
diff --git a/stun/stunagent.c b/stun/stunagent.c
index 26adb9f..e873e55 100644
--- a/stun/stunagent.c
+++ b/stun/stunagent.c
@@ -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;
diff --git a/stun/stunagent.h b/stun/stunagent.h
index 95e89fd..fba972e 100644
--- a/stun/stunagent.h
+++ b/stun/stunagent.h
@@ -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;
diff --git a/stun/stunmessage.h b/stun/stunmessage.h
index 0ac9977..cfa5ca2 100644
--- a/stun/stunmessage.h
+++ b/stun/stunmessage.h
@@ -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 */
diff --git a/tests/meson.build b/tests/meson.build
index 65ac23a..685d11d 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -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')
diff --git a/tests/test-consent.c b/tests/test-consent.c
new file mode 100644
index 0000000..8159c7e
--- /dev/null
+++ b/tests/test-consent.c
@@ -0,0 +1,520 @@
+/*
+ * 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;
+}