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

agent: implement support for RFC7675 - Consent Freshness

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

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

Slight differences from the existing keepalive connection checks
include:
- an additional consent expiry timer instead of relying on all binding
  requests to succeed.
- 403: 'Forbidden' stun error-code which revokes consent with immediate
  effect.
parent 3492be60
......@@ -61,6 +61,8 @@ ICE
STUN
http://tools.ietf.org/html/rfc3489 (old)
http://tools.ietf.org/html/rfc5389
STUN Consent Freshness RFC
https://tools.ietf.org/html/rfc7675
TURN
http://tools.ietf.org/html/rfc5766
RTP
......
......@@ -106,6 +106,10 @@ nice_input_message_iter_compare (const NiceInputMessageIter *a,
#define NICE_AGENT_TIMER_TA_DEFAULT 20 /* timer Ta, msecs (impl. defined) */
#define NICE_AGENT_TIMER_TR_DEFAULT 25000 /* timer Tr, msecs (impl. defined) */
#define NICE_AGENT_TIMER_CONSENT_DEFAULT 5000 /* msec timer consent freshness connchecks (RFC 7675) */
#define NICE_AGENT_TIMER_CONSENT_TIMEOUT 10000 /* msec timer for consent checks to timeout and assume consent lost (RFC 7675) */
#define NICE_AGENT_TIMER_MIN_CONSENT_INTERVAL 4000 /* msec timer minimum for consent lost requests (RFC 7675) */
#define NICE_AGENT_TIMER_KEEPALIVE_TIMEOUT 50000 /* msec timer for keepalive (without consent checks) to timeout and assume conection lost */
#define NICE_AGENT_MAX_CONNECTIVITY_CHECKS_DEFAULT 100 /* see RFC 8445 6.1.2.5 */
......@@ -184,6 +188,8 @@ struct _NiceAgent
guint conncheck_ongoing_idle_delay; /* ongoing delay before timer stop */
gboolean controlling_mode; /* controlling mode used by the
conncheck */
gboolean consent_freshness; /* rfc 7675 consent freshness with
connchecks */
/* XXX: add pointer to internal data struct for ABI-safe extensions */
};
......
......@@ -123,6 +123,7 @@ enum
PROP_ICE_TRICKLE,
PROP_SUPPORT_RENOMINATION,
PROP_IDLE_TIMEOUT,
PROP_CONSENT_FRESHNESS,
};
......@@ -773,6 +774,8 @@ nice_agent_class_init (NiceAgentClass *klass)
* This is always enabled if the compatibility mode is
* %NICE_COMPATIBILITY_GOOGLE.
*
* This is always enabled if the 'consent-freshness' property is %TRUE
*
* Since: 0.1.8
*/
g_object_class_install_property (gobject_class, PROP_KEEPALIVE_CONNCHECK,
......@@ -890,6 +893,28 @@ nice_agent_class_init (NiceAgentClass *klass)
FALSE,
G_PARAM_READWRITE));
/**
* NiceAgent:consent-freshness
*
* Whether to perform periodic consent freshness checks as specified in
* RFC 7675. When %TRUE, the agent will periodically send binding requests
* to the peer to maintain the consent to send with the peer. On receipt
* of any authenticated error response, a component will immediately move
* to the failed state.
*
* Setting this property to %TRUE implies that 'keepalive-conncheck' should
* be %TRUE as well.
*
* Since: 0.1.20
*/
g_object_class_install_property (gobject_class, PROP_CONSENT_FRESHNESS,
g_param_spec_boolean (
"consent-freshness",
"Consent Freshness",
"Whether to perform the consent freshness checks as specified in RFC 7675",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/* install signals */
/**
......@@ -1314,6 +1339,7 @@ nice_agent_new_full (GMainContext *ctx,
"full-mode", (flags & NICE_AGENT_OPTION_LITE_MODE) ? FALSE : TRUE,
"ice-trickle", (flags & NICE_AGENT_OPTION_ICE_TRICKLE) ? TRUE : FALSE,
"support-renomination", (flags & NICE_AGENT_OPTION_SUPPORT_RENOMINATION) ? TRUE : FALSE,
"consent-freshness", (flags & NICE_AGENT_OPTION_CONSENT_FRESHNESS) ? TRUE : FALSE,
NULL);
return agent;
......@@ -1438,7 +1464,7 @@ nice_agent_get_property (
break;
case PROP_KEEPALIVE_CONNCHECK:
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE)
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE || agent->consent_freshness)
g_value_set_boolean (value, TRUE);
else
g_value_set_boolean (value, agent->keepalive_conncheck);
......@@ -1464,6 +1490,10 @@ nice_agent_get_property (
g_value_set_boolean (value, agent->use_ice_trickle);
break;
case PROP_CONSENT_FRESHNESS:
g_value_set_boolean (value, agent->consent_freshness);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
......@@ -1502,9 +1532,14 @@ nice_agent_init_stun_agent (NiceAgent *agent, StunAgent *stun_agent)
STUN_AGENT_USAGE_USE_FINGERPRINT |
STUN_AGENT_USAGE_NO_ALIGNED_ATTRIBUTES);
} else {
StunAgentUsageFlags stun_usage = 0;
if (agent->consent_freshness)
stun_usage |= STUN_AGENT_USAGE_CONSENT_FRESHNESS;
stun_agent_init (stun_agent, STUN_ALL_KNOWN_ATTRIBUTES,
STUN_COMPATIBILITY_RFC5389,
STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
stun_usage | STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
STUN_AGENT_USAGE_USE_FINGERPRINT);
}
stun_agent_set_software (stun_agent, agent->software_attribute);
......@@ -1677,6 +1712,10 @@ nice_agent_set_property (
agent->use_ice_trickle = g_value_get_boolean (value);
break;
case PROP_CONSENT_FRESHNESS:
agent->consent_freshness = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
......@@ -5187,6 +5226,13 @@ nice_agent_send_messages_nonblocking_internal (
goto done;
}
if (component->selected_pair.local != NULL &&
!component->selected_pair.remote_consent.have) {
g_set_error (&child_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
"Consent to send has been revoked by the peer");
goto done;
}
/* FIXME: Cancellation isn’t yet supported, but it doesn’t matter because
* we only deal with non-blocking writes. */
if (component->selected_pair.local != NULL) {
......@@ -5965,6 +6011,8 @@ nice_agent_set_selected_pair (
NICE_COMPONENT_STATE_READY);
/* step: set the selected pair */
/* XXX: assume we have consent to send to this selected remote address */
pair.remote_consent.have = TRUE;
nice_component_update_selected_pair (agent, component, &pair);
agent_signal_new_selected_pair (agent, stream_id, component_id,
(NiceCandidate *) pair.local, (NiceCandidate *) pair.remote);
......@@ -7103,3 +7151,28 @@ nice_agent_get_sockets (NiceAgent *agent, guint stream_id, guint component_id)
return array;
}
NICEAPI_EXPORT gboolean
nice_agent_consent_lost (
NiceAgent *agent,
guint stream_id,
guint component_id)
{
gboolean result = FALSE;
NiceComponent *component;
agent_lock (agent);
if (!agent->consent_freshness) {
g_warning ("Agent %p: Attempt made to signal consent lost for "
"stream/component %u/%u but RFC7675/consent-freshness is not enabled "
"for this agent. Ignoring request", agent, stream_id, component_id);
} else if (agent_find_component (agent, stream_id, component_id, NULL, &component)) {
nice_debug ("Agent %p: local consent lost for stream/component %u/%u", agent,
component->stream_id, component->id);
component->have_local_consent = FALSE;
result = TRUE;
}
agent_unlock_and_emit (agent);
return result;
}
......@@ -407,6 +407,7 @@ typedef enum
* @NICE_AGENT_OPTION_ICE_TRICKLE: Enable ICE trickle mode
* @NICE_AGENT_OPTION_SUPPORT_RENOMINATION: Enable renomination triggered by NOMINATION STUN attribute
* proposed here: https://tools.ietf.org/html/draft-thatcher-ice-renomination-00
* @NICE_AGENT_OPTION_CONSENT_FRESHNESS: Enable RFC 7675 consent freshness support. (Since: 0.1.20)
*
* These are options that can be passed to nice_agent_new_full(). They set
* various properties on the agent. Not including them sets the property to
......@@ -420,6 +421,7 @@ typedef enum {
NICE_AGENT_OPTION_LITE_MODE = 1 << 2,
NICE_AGENT_OPTION_ICE_TRICKLE = 1 << 3,
NICE_AGENT_OPTION_SUPPORT_RENOMINATION = 1 << 4,
NICE_AGENT_OPTION_CONSENT_FRESHNESS = 1 << 5,
} NiceAgentOption;
/**
......@@ -919,6 +921,9 @@ nice_agent_get_remote_candidates (
* "ICE Restarts"), as well as when reacting (spec section 9.2.1.1.
* "Detecting ICE Restart") to a restart.
*
* If consent-freshness has been enabled on @agent, as specified in RFC7675
* then restarting streams will restore the local consent.
*
* Returns: %TRUE on success %FALSE on error
**/
gboolean
......@@ -938,6 +943,9 @@ nice_agent_restart (
* Unlike nice_agent_restart(), this applies to a single stream. It also
* does not generate a new tie breaker.
*
* If consent-freshness has been enabled on @agent, as specified in RFC7675
* then restart @stream_id will restore the local consent for that stream.
*
* Returns: %TRUE on success %FALSE on error
*
* Since: 0.1.6
......@@ -1659,6 +1667,34 @@ nice_agent_peer_candidate_gathering_done (
NiceAgent *agent,
guint stream_id);
/**
* nice_agent_consent_lost:
* @agent: The #NiceAgent Object
* @stream_id: The ID of the stream
* @component_id: The ID of the component
*
* Notifies the agent that consent to receive has been revoked. This will
* cause the component to fail with 403 'Forbidden' all incoming STUN binding
* requests as specified in RFC 7675.
*
* A stream with a component in the consent-lost state can be reused by
* performing an ice restart with nice_agent_restart() or
* nice_agent_restart_stream().
*
* Calling the function only has an effect when @agent has been created with
* @NICE_AGENT_OPTION_CONSENT_FRESHNESS.
*
* Returns: %FALSE if the stream or component could not be found or consent
* freshness is not enabled, %TRUE otherwise
*
* Since: 0.1.20
*/
gboolean
nice_agent_consent_lost (
NiceAgent *agent,
guint stream_id,
guint component_id);
/**
* nice_agent_close_async:
* @agent: The #NiceAgent object
......
......@@ -307,10 +307,10 @@ nice_component_clean_turn_servers (NiceAgent *agent, NiceComponent *cmp)
static void
nice_component_clear_selected_pair (NiceComponent *component)
{
if (component->selected_pair.keepalive.tick_source != NULL) {
g_source_destroy (component->selected_pair.keepalive.tick_source);
g_source_unref (component->selected_pair.keepalive.tick_source);
component->selected_pair.keepalive.tick_source = NULL;
if (component->selected_pair.remote_consent.tick_source != NULL) {
g_source_destroy (component->selected_pair.remote_consent.tick_source);
g_source_unref (component->selected_pair.remote_consent.tick_source);
component->selected_pair.remote_consent.tick_source = NULL;
}
memset (&component->selected_pair, 0, sizeof(CandidatePair));
......@@ -458,6 +458,8 @@ nice_component_restart (NiceComponent *cmp)
/* Reset the priority to 0 to make sure we get a new pair */
cmp->selected_pair.priority = 0;
cmp->have_local_consent = TRUE;
/* note: component state managed by agent */
}
......@@ -499,6 +501,7 @@ nice_component_update_selected_pair (NiceAgent *agent, NiceComponent *component,
component->selected_pair.remote = pair->remote;
component->selected_pair.priority = pair->priority;
component->selected_pair.stun_priority = pair->stun_priority;
component->selected_pair.remote_consent.have = pair->remote_consent.have;
nice_component_add_valid_candidate (agent, component,
(NiceCandidate *) pair->remote);
......@@ -580,6 +583,7 @@ nice_component_set_selected_remote_candidate (NiceComponent *component,
component->selected_pair.local = (NiceCandidateImpl *) local;
component->selected_pair.remote = (NiceCandidateImpl *) remote;
component->selected_pair.priority = priority;
component->selected_pair.remote_consent.have = TRUE;
/* Get into fallback mode where packets from any source is accepted once
* this has been called. This is the expected behavior of pre-ICE SIP.
......@@ -1107,6 +1111,8 @@ nice_component_init (NiceComponent *component)
g_queue_init (&component->queued_tcp_packets);
g_queue_init (&component->incoming_checks);
component->have_local_consent = TRUE;
}
static void
......
......@@ -65,17 +65,23 @@ G_BEGIN_DECLS
typedef struct _CandidatePair CandidatePair;
typedef struct _CandidatePairKeepalive CandidatePairKeepalive;
typedef struct _CandidatePairConsentCheck CandidatePairConsentCheck;
typedef struct _IncomingCheck IncomingCheck;
struct _CandidatePairKeepalive
{
guint64 next_tick; /* next tick timestamp */
GSource *tick_source;
guint stream_id;
guint component_id;
StunTimer timer;
uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6];
StunMessage stun_message;
};
struct _CandidatePairConsentCheck
{
GSource *tick_source;
gboolean have;
guint64 last_received; /* g_get_monotonic_time() of last remote
consent received */
};
struct _CandidatePair
......@@ -85,6 +91,7 @@ struct _CandidatePair
guint64 priority; /* candidate pair priority */
guint32 stun_priority;
CandidatePairKeepalive keepalive;
CandidatePairConsentCheck remote_consent;
};
struct _IncomingCheck
......@@ -224,6 +231,8 @@ struct _NiceComponent {
* ACKs on. The messages are dequeued to the pseudo-TCP socket once a selected
* UDP socket is available. This is only used for reliable Components. */
GQueue queued_tcp_packets;
gboolean have_local_consent;
};
typedef struct {
......
This diff is collapsed.
......@@ -49,6 +49,11 @@
#define NICE_CANDIDATE_PAIR_MAX_FOUNDATION NICE_CANDIDATE_MAX_FOUNDATION*2
/* A helper macro to test whether connection checks should continue to be
* performed after a component has successfully connected */
#define NICE_AGENT_DO_KEEPALIVE_CONNCHECKS(obj) \
((obj)->consent_freshness || (obj)->keepalive_conncheck || (obj)->compatibility == NICE_COMPATIBILITY_GOOGLE)
/**
* NiceCheckState:
* @NICE_CHECK_WAITING: Waiting to be scheduled.
......
......@@ -121,6 +121,10 @@
<title>Index of new symbols in 0.1.18</title>
<xi:include href="xml/api-index-0.1.18.xml"><xi:fallback/></xi:include>
</index>
<index role="0.1.20">
<title>Index of new symbols in 0.1.20</title>
<xi:include href="xml/api-index-0.1.20.xml"><xi:fallback/></xi:include>
</index>
<xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
</part>
</book>
......@@ -57,6 +57,7 @@ nice_agent_get_selected_socket
nice_agent_get_sockets
nice_agent_get_component_state
nice_agent_close_async
nice_agent_consent_lost
nice_component_state_to_string
<SUBSECTION Standard>
NICE_AGENT
......
......@@ -18,6 +18,7 @@ nice_address_to_string
nice_agent_add_local_address
nice_agent_add_stream
nice_agent_close_async
nice_agent_consent_lost
nice_agent_recv
nice_agent_recv_messages
nice_agent_recv_nonblocking
......
......@@ -337,6 +337,13 @@ StunValidationStatus stun_agent_validate (StunAgent *agent, StunMessage *msg,
}
}
if (agent->usage_flags & STUN_AGENT_USAGE_CONSENT_FRESHNESS &&
stun_message_get_class (msg) == STUN_ERROR) {
stun_message_find_error (msg, &error_code);
if (error_code == STUN_ERROR_FORBIDDEN) {
return STUN_VALIDATION_FORBIDDEN;
}
}
if (sent_id_idx != -1 && sent_id_idx < STUN_AGENT_MAX_SAVED_IDS) {
agent->sent_ids[sent_id_idx].valid = FALSE;
......
......@@ -123,6 +123,9 @@ typedef enum {
* @STUN_VALIDATION_UNKNOWN_ATTRIBUTE: The message is valid but contains one
* or more unknown comprehension attributes. This is a response, or error,
* or indication message and no error response should be sent
* @STUN_VALIDATION_FORBIDDEN: The message response is valid and indicates
* the peer responded with the error code 403 'Forbidden'. No response
* should be sent.
*
* This enum is used as the return value of stun_agent_validate() and represents
* the status result of the validation of a STUN message.
......@@ -137,6 +140,7 @@ typedef enum {
STUN_VALIDATION_UNMATCHED_RESPONSE,
STUN_VALIDATION_UNKNOWN_REQUEST_ATTRIBUTE,
STUN_VALIDATION_UNKNOWN_ATTRIBUTE,
STUN_VALIDATION_FORBIDDEN,
} StunValidationStatus;
/**
......@@ -168,6 +172,9 @@ typedef enum {
* @STUN_AGENT_USAGE_NO_ALIGNED_ATTRIBUTES: The agent should not assume STUN
* attributes are aligned on 32-bit boundaries when parsing messages and also
* do not add padding when creating messages.
* @STUN_AGENT_USAGE_CONSENT_FRESHNESS: The agent should expect and use
* the %STUN_VALIDATION_FORBIDDEN return value from ERROR-CODE responses and
* abort all transactions accordingly.
*
* This enum defines a bitflag usages for a #StunAgent and they will define how
* the agent should behave, independently of the compatibility mode it uses.
......@@ -183,6 +190,7 @@ typedef enum {
STUN_AGENT_USAGE_NO_INDICATION_AUTH = (1 << 5),
STUN_AGENT_USAGE_FORCE_VALIDATER = (1 << 6),
STUN_AGENT_USAGE_NO_ALIGNED_ATTRIBUTES = (1 << 7),
STUN_AGENT_USAGE_CONSENT_FRESHNESS = (1 << 8),
} StunAgentUsageFlags;
......
......@@ -414,6 +414,8 @@ typedef uint8_t StunTransactionId[STUN_MESSAGE_TRANS_ID_LEN];
* "Bad Request" error as defined in RFC5389
* @STUN_ERROR_UNAUTHORIZED: The ERROR-CODE value for the
* "Unauthorized" error as defined in RFC5389
* @STUN_ERROR_FORBIDDEN: The ERROR-CODE value for the
* "Forbidden" error as defined in RFC7675
* @STUN_ERROR_UNKNOWN_ATTRIBUTE: The ERROR-CODE value for the
* "Unknown Attribute" error as defined in RFC5389
* @STUN_ERROR_ALLOCATION_MISMATCH:The ERROR-CODE value for the
......@@ -457,6 +459,7 @@ typedef enum
STUN_ERROR_TRY_ALTERNATE=300, /* RFC5389 */
STUN_ERROR_BAD_REQUEST=400, /* RFC5389 */
STUN_ERROR_UNAUTHORIZED=401, /* RFC5389 */
STUN_ERROR_FORBIDDEN=403, /* RFC7675 */
STUN_ERROR_UNKNOWN_ATTRIBUTE=420, /* RFC5389 */
STUN_ERROR_ALLOCATION_MISMATCH=437, /* TURN-12 */
STUN_ERROR_STALE_NONCE=438, /* RFC5389 */
......
......@@ -28,7 +28,8 @@ nice_tests = [
'test-drop-invalid',
'test-nomination',
'test-interfaces',
'test-set-port-range'
'test-set-port-range',
'test-consent',
]
if cc.has_header('arpa/inet.h')
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment