diff options
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | agent/agent-priv.h | 6 | ||||
-rw-r--r-- | agent/agent.c | 77 | ||||
-rw-r--r-- | agent/agent.h | 36 | ||||
-rw-r--r-- | agent/component.c | 14 | ||||
-rw-r--r-- | agent/component.h | 15 | ||||
-rw-r--r-- | agent/conncheck.c | 256 | ||||
-rw-r--r-- | agent/conncheck.h | 5 | ||||
-rw-r--r-- | docs/reference/libnice/libnice-docs.xml | 4 | ||||
-rw-r--r-- | docs/reference/libnice/libnice-sections.txt | 1 | ||||
-rw-r--r-- | nice/libnice.sym | 1 | ||||
-rw-r--r-- | stun/stunagent.c | 7 | ||||
-rw-r--r-- | stun/stunagent.h | 8 | ||||
-rw-r--r-- | stun/stunmessage.h | 3 | ||||
-rw-r--r-- | tests/meson.build | 3 | ||||
-rw-r--r-- | tests/test-consent.c | 520 |
16 files changed, 830 insertions, 128 deletions
@@ -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; +} |