libnice/libnice-0.1.14-20171128.patch

6712 lines
254 KiB
Diff
Raw Normal View History

2018-01-29 15:56:32 +00:00
commit fb2f1f77a31baa91968fc81c205f980b6913f403
Author: Youness Alaoui <kakaroto@kakaroto.homelinux.net>
Date: Tue Nov 28 16:05:18 2017 -0500
conncheck: handle alternate-server for turn relays differently
If a relay gives us an alternate-server, we need to cancel and reset
every candidate discovery attempt that uses the same server, to avoid
ending up with one component on one server and the other component on
another server (causing relay candidates with mismatched foundations).
commit 4172d48852ecd1c86cc7bd4665b23697603d1eed
Author: Youness Alaoui <kakaroto@kakaroto.homelinux.net>
Date: Tue Nov 28 15:14:11 2017 -0500
discovery: Increase discovery_unsched_items whenever we restart a check
The discovery_unsched_items is decremented every time a DiscoveryCandidate
goes from non-pending to pending. So if we restart a check by setting
pending to FALSE, we should re-increase the discovery_unsched_items.
commit 59fcf95d505c3995f858b826d10cd48321ed383e
Author: Youness Alaoui <kakaroto@kakaroto.homelinux.net>
Date: Mon Nov 27 17:07:02 2017 -0500
turn: Add support for ALTERNATE_SERVER in OC2007 Compatibility
The MS Office TURN servers will always return the MS_ALTERNATE_SERVER in
allocation responses, and if they are not handled, we end up using the
main turn server to send allocation requests that then get sent to the
alternate server which will return the XOR_MAPPED_ADDRESS containing
the IP address of the turn server that proxied the message instead of
our own actual external IP.
commit 17f30e4465efe9533799b02d6f95feeaf0f2748c
Author: Miguel París <mparisdiaz@gmail.com>
Date: Wed Nov 8 16:26:47 2017 +0000
conncheck: do not require that all streams have a connection check list
One or more streams might not have any connection check list if the
number of streams differs from the peer agent.
Differential Revision: https://phabricator.freedesktop.org/D1880
commit c63349894b3fe974494453a883dfb5ad05df5a46
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Thu Nov 23 18:31:31 2017 +0100
Makefile: really enable debug for tests
Differential Revision: https://phabricator.freedesktop.org/D1888
commit 02216a6766caccb652387d5ee19686149eedbc93
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Nov 21 15:12:45 2017 +0100
agent: prevent external role change while conncheck is running
With this patch, we stash the controlling mode property change, and
apply it safely, when it won't interfere with an ongoing conncheck
running. According to RFC5245, sect 5.2. "Determining Role", the role
is determined for a session, and persists unless an ICE is restarted.
Differential Revision: https://phabricator.freedesktop.org/D1887
commit fbdccf0c2787ebdc65fe13ac64bd25c829ea7972
Author: Philip Withnall <withnall@endlessm.com>
Date: Thu Aug 3 12:20:32 2017 +0100
stun: Fix FD leak in test/utility code
https://phabricator.freedesktop.org/T7798
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Reviewed-by: Olivier Crête <olivier.crete@collabora.com>
Differential Revision: https://phabricator.freedesktop.org/D1819
commit 4c4834ab634f735145c8f758a22cbdd9cab79bac
Author: Philip Withnall <withnall@endlessm.com>
Date: Tue Sep 12 13:23:53 2017 +0100
tests: Fix agent.h header inclusion in test-gstreamer.c
Spotted by Lukas Gradl on the mailing list.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
commit 1a1803a45778000720c93d91060cedeb19124a27
Author: Philip Withnall <withnall@endlessm.com>
Date: Tue Sep 12 13:23:31 2017 +0100
tests: Fix copyright dates in test-gstreamer.c
This code is not 1000 years old.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
commit 14102d44449d2eb4148588ce54fa897fa13b87ad
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Sun Jul 2 16:02:09 2017 +0200
conncheck: change state before updating nominated pairs
When a pair is nominated while in state failed, we first move
back to state connecting, then we update the selected pair, and
finally we move to state connected.
commit 72dd26a3368d3506fe8faca7067a02784fb5f0fd
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Wed Jun 28 12:06:48 2017 +0200
conncheck: forgot to put a pair in triggered check list
When a new pair is created from an unknown remote candidate, it
should be enqueue for a triggered check, to allow it to be marked
as nominated on response arrival in priv_mark_pair_nominated().
Creating it in waiting state is not sufficient since the update
in priv_mark_pair_nominated() from previous commits.
Differential Revision: https://phabricator.freedesktop.org/D1763
commit 6fe64fdbc53ab87dffd79972f492665cff14c0a0
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Jun 27 11:01:14 2017 +0200
conncheck: update the state of triggered checks pairs
With this patch, we fix an ambiguity of some parts of the spec, when
the document refers to in-progress pairs, that also concern pairs in
the triggered checks list.
The first cast is in section 7.1.2.5, "Updating the Nominated Flag",
when the in-progress pair will be nominated on response arrival. This is
handled in function priv_mark_pair_nominated(), when a pair is put to
the triggered check list in reaction to a matching inbound stun request.
Such a pair in priv_mark_pair_nominated() will _always_ be in the
triggered check list, from the previously called function
priv_schedule_triggered_check().
The second case is in section 8.1.2, "Updating State" when an in-progress
pair stops its retransmission when another pair of higher priority is
already nominated. This is handled by function priv_prune_pending_checks().
Until now, pairs enqueued in the triggered check list move transiently
to state waiting, according to 7.2.1.4. But this state causes wrong
decisions in the two previous cases, because such pairs should in fact
rather be considered "like in-progress", to avoid discarding them
inadvertantly.
This patch update the state of the triggered check list
pairs to in-progress. It allows to remove exception handling cited
above: the code is a bit more simple, and allows some refactoring
in priv_mark_pair_nominated() between RFC and compatibility modes.
Differential Revision: https://phabricator.freedesktop.org/D1762
commit 36f306f4a95f1c2b3e9c584b5a645a78e231c020
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Mon Jun 26 21:41:44 2017 +0200
conncheck: support several stun requests per pair
This patch should improve the reliabily of the connection check by
keeping the record of several simultaneous ongoing stun requests per
pair. A new stun request on an in-progress pair typically is caused by
in inbound stun request from the peer on this same pair. This is named
"Triggered Checks" in the spec. When this situation arises, it is fair
to handle these two stun requests simultaneously, the triggered check,
and the initial ordinary check, since both can potentially succeed.
Differential Revision: https://phabricator.freedesktop.org/D1761
commit e860948b5fe3a791119957f26045b8f5159baeff
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Mon Jun 26 21:06:36 2017 +0200
conncheck: use stun_timer_remainder less frequently
We try to use stun_timer_remainder() less frequently, particularily
in the debug messages, and favour of the next_tick value associated
to the pair.
Differential Revision: https://phabricator.freedesktop.org/D1760
commit ce33747e6fc9ca59ea22d54e3b5a9a67b69d8141
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Mon Jun 26 20:41:49 2017 +0200
conncheck: make debug sentences more accurate
We add a helper function to print the pair state in-extenso.
Differential Revision: https://phabricator.freedesktop.org/D1759
commit 25be00271a4c8c684a2d435d29ae0811dbf5e21c
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Mon Jun 26 20:36:35 2017 +0200
conncheck: reorder some chunks of code
With this patch we simplify the levels of code indentation.
Differential Revision: https://phabricator.freedesktop.org/D1758
commit 5a42089aeb2dbbb52d820cd1b6efdfcfbe9b055e
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Sep 5 14:50:29 2017 -0400
agent: Set error if it isn't set
commit dbaf8f5ccd76089e340883887c7e08e6c04de80a
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 12 13:22:21 2016 +0200
conncheck: improve role conflict debug
This patch displays explicitely the controlling or controlled
role of the agent.
Differential Revision: https://phabricator.freedesktop.org/D874
commit 9f800d3597767855accccc592c34bc4e945f5bd5
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Wed Jun 21 20:42:57 2017 -0400
configure: Remove -Wswitch-enum
Creates useless warnings when other libraries change.
https://phabricator.freedesktop.org/T7770
commit 63d273cea42def3567701ad9feab91f63cf9345f
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Thu Feb 11 22:16:48 2016 -0500
component: Use non-GClosure dummy callbacks
GClosures are not that cheap to setup
commit 2c50d73b82f2ec2422a8e0ea393194486c193c64
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Wed Feb 10 23:20:39 2016 -0500
agent: Don't crash if recv cancelled without a GError
commit dcb0d647174416a292492f8deca86f83a2ef124c
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Wed Jun 21 17:07:17 2017 -0400
Repleace UNRELEASED with 0.1.15
commit e3ddaa285e389baf3f26cfb6964919718a8f6a00
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Wed Jun 21 16:55:32 2017 -0400
agent: Adjust the nice_agent_new_full() to use flags
This makes it easier to read and more extensible.
commit c7a5a92b66f9b83baf2aa446966bdfb2cf39ecd1
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Sun Jun 18 10:12:58 2017 +0200
agent: remove spurious newlines
Differential Revision: https://phabricator.freedesktop.org/D1756
commit b4b8d6628c8c5d4f10af0101f846db4938a3f6c4
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Sun May 28 22:20:36 2017 +0200
stun: fix gcc7 implicit fallthrough warning
Differential Revision: https://phabricator.freedesktop.org/D1754
commit 195db6b344fc4f9fadc39419dfeec2fc14b23fac
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Fri Jul 15 23:31:42 2016 +0200
agent: add new pairs only for gathering streams
At the end of the local candidate gathering process, we only create new
pairs for streams that are in gathering state.
Other stream that may be in ready state for example, due to a
previously succeeded conncheck process, may have accumulated some
couples (local,remote) candidates that have not resulted in the creation
a new pair during this previous conncheck process, and we don't want
these new pairs to be added now, because it would generate unneeded
transition changes for a stream unconcerned by this gathering.
Differential Revision: https://phabricator.freedesktop.org/D1755
commit 07366a5bca7e4818b8df29d9c7c220da8f752547
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Jun 21 21:47:42 2016 +0200
conncheck: fix the component failed transition
This patch fixes the transition of a component from connecting to
failed, that previously occured due to the propagation of the
keep_timer_going variable, and to the final call to function
priv_update_check_list_failed_components(), after the global agent
timer was stopped.
Previously, the code almost never entered to failed state, because the
timer was going one, as long as the number of nominated pair was not
enough, and as long as there were valid pairs not yet nominated. Even
if all pair timers were over.
The definition of the Failed state of a conncheck list is somewhat
contradictory in the spec, depending on weather you read :
* sect 5.7.4. "Computing States",
"Failed: In this state, the ICE checks have not completed successfully
for this media stream."
or
* sect 7.1.3.3. "Check List and Timer State Updates",
"If all of the pairs in the check list are now either in the Failed or
Succeeded state: If there is not a pair in the valid list for each
component of the media stream, the state of the check list is set to
Failed."
Our understanding of the ICE spec is that, the proper way to enter failed
state instead in when all connchecks have no longer in-progress pairs.
All pairs are either in state succeeded, discovered, or failed. No timer
is still running, and we have no hope that the conncheck list changes
again, except if an unexpected STUN packet arrives later. All pairs in
frozen state is a special case, that is handled separately (sect
7.1.3.3).
A special grace delay is added before declaring a component in state
Failed. This delay is not part of the RFC, and it is aimed to limit the
cases when a conncheck list is reactivated just after it's been declared
failed, causing a user visible transition from connecting to failed, and
back from failed to connecting again. This is also required by the test
suite, that counts exactly the number of time each state is entered, and
doesn't expect these transcient failed states to happen (frequent due to
the nature of the testsuite, less frequent in real life).
Differential Revision: https://phabricator.freedesktop.org/D1111
commit 95f8805eb7b77755337e28daf1f134587d42b35f
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Thu Jun 16 17:32:39 2016 +0200
conncheck: remove cancelled pair state
Pairs with the state NICE_CHECK_CANCELLED are the pairs targeted for
removal after the nomination of a pair with an higher priority,
described in Section 8.1.2 "Updating States", item 2 of RFC 5245. They
include also pairs that overflow the conncheck list size, but this is a
somewhat more marginal situation. So we are mainly interested in the
first use case of this state.
This state mixes two different situations, that deserve a distinct
handling : on one side, there are waiting or frozen pairs that must be
removed, this is an immediate action that doesn't need a dedicated state
for that. And on the other side, there are in-progress pairs that
should no longer be retransmitted, because another pair with a higher
priority has already been nominated.
This patch removes the cancelled state, and adds a flag
retransmit_on_timeout to deal with this last situation. Note that this
case should not generate a triggered check, as per described in section
7.2.1.4, when the state of the pair is In-Progress or Failed, since this
pair of lower priority has no hope to replace the nominated one.
Differential Revision: https://phabricator.freedesktop.org/D1114
commit d516fca1b0e0a6606afec797bdc0690104e779a9
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Jun 14 21:32:26 2016 +0200
conncheck: adjust recheck on timeout strategy
The pair recheck on timeout can easily cause repetitive rechecks in
a ping-pong effect, if both peers with the same behaviour try to
check the same pair almost simultaneously, and if the network rtt
is greater than the initial timer rto. The reply to the initial
stun request may arrive after the in-progress conncheck
cancellation (described in RFC 5245, sect 7.2.1.4). Cancellation
creates a new stun request, and forgets the initial one.
The conncheck timer is restarted with the same initial value,
so the same situation happens again later.
We choose to avoid resetting the timer in such situation. After enough
retransmissions, the timeout delay, that doubles after each timeout,
becomes longer than the rtt, and the stun reply can be handled.
Differential Revision: https://phabricator.freedesktop.org/D1115
commit f19d209decac432a1597d84c3d5809d2208f7457
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Jun 14 21:20:49 2016 +0200
conncheck: do not recheck a just succeeded pair
We cancel the potential in-progress transaction cancellation, caused by
sect 7.2.1.4 "Triggered Checks", when we receive a valid reply before
transmission timeout, or just after timeout, when the pair is
temporarily put on the triggered check list on the way to be
rechecked. This situation is not covered by the RFC 5245.
Differential Revision: https://phabricator.freedesktop.org/D1119
commit 59fe48517c0b7db77b99183d31fdd84b55adb5d4
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Jun 14 21:12:16 2016 +0200
conncheck: fix a state transition case
When a new stun request hits a valid pair, of a failed component, we may
have a transition from state failed to connected. In this situation, we
do a logical progression failed -> connecting -> connected, like we do
in function priv_update_check_list_state_for_ready()
Similarily, when a new stun request hits a failed pair, of a failed
component, triggering a new conncheck for this pair may also cause the
component state to move back from failed to connecting state.
Differential Revision: https://phabricator.freedesktop.org/D1118
commit 6a512b6eca9603ce8bf3ed0814fd314684c66ea7
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Jun 14 21:04:49 2016 +0200
conncheck: try to change earlier to state ready
We check if we can move from state connected to ready just
after a pair expired its retransmission count. This pair
will be marked failed, and will no longer be in-progress.
The number of in-progress dropping down to zero is one
of the conditions needed to make the transition to ready,
per component (and not globally as it's the case in other
locations where this check function is called).
Differential Revision: https://phabricator.freedesktop.org/D1117
commit 8fa648a15a6700d08165fe97a09f5c068abae1e6
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Mon Apr 11 13:13:51 2016 +0200
conncheck: dont cancel a pair for triggered check
This patch adds another supplementary "corner" case, not covered by the
ICE spec, sect 8.1.2, "Updating States". A pair in waiting state and in
the triggered check list should be considered like an in-progress pair,
and cancelled only if its priority is lower than the priority of the
nominated pair. This is required in some aggressive nomination
situations for both peers to select the same pair, having the highest
priority.
Differential Revision: https://phabricator.freedesktop.org/D933
commit 11d4e37a030eb144a355dc26c705ef5aa5a975a7
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Fri Apr 1 17:31:44 2016 +0200
conncheck: remove a useless pair recheck
This exception to the ICE spec is no longer needed: when a pair is in
the succeeded state, there is no needed to recheck it again upon
reception of an incoming stun request on it.
Differential Revision: https://phabricator.freedesktop.org/D884
commit 25b3eeec70b4e8e3b2154a18cdc8c5604f572012
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 12 12:56:28 2016 +0200
conncheck: update the pair state in triggered check list
With this patch, we update the state of the pair to waiting when
it is put in the triggered check queue. We also take care to call
priv_schedule_triggered_check() before priv_mark_pair_nominated()
so a pair to be rechecked and put on the triggered check queue
will have a unique state to be tested in the following call to
priv_mark_pair_nominated() when evaluating its nomination attributes.
Differential Revision: https://phabricator.freedesktop.org/D883
commit afd8d41bb34afb3864e838ef79026ae4ef15c0d4
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 12 13:32:49 2016 +0200
conncheck: new pairs never have the nominated flag preset
This patch disables the possibility to set the nominated flag of a
candidate pair at creation time. This possibility was used when a new
pair is created from a new peer reflexive remote candidate, when the
agent is in controlled mode, and an stun request with USE-CANDIDATE is
received. In this case, since previous commit "conncheck: fix a
nomination corner case", we set the nominated flag when the stun
response of this new pair will arrive, and not before. Consequently,
this flag is no longer required when the pair is created.
Differential Revision: https://phabricator.freedesktop.org/D881
commit 3916b8bcbf7e78e1dcb6b77882075c2c22719b4e
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 12 13:30:04 2016 +0200
conncheck: fix a nomination corner case
This patch add two supplementary cases, not covered by the ICE spec,
sect 7.2.1.5 "Updating the Nominated Flag" when a controlled agent
receives a STUN request with the USE-CANDIDATE flag, for a pair that is
in the waiting state. We consider that this case is similar to the
in-progress state, and should be handled in the same way. We also accept
when the pair is in frozen state. This latter case happens in the
new-dribble test, when an agent replays incoming early connchecks.
Differential Revision: https://phabricator.freedesktop.org/D880
commit 9103a5f2e184211fc160d1d3070ce4d043c71ff0
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 19 18:16:26 2016 +0200
conncheck: use the right pair when retriggering a check
This patch completes the previous patch by adding a link back from the
discovered pair, to the parent pair that generated this check. This link
is needed by the ICE spec, to comply with section 8.1.1.1, "Regular
nomination", where the check to be retriggered is the initial check that
caused the discovery of the valid pair. When the valid pair is a
peer-reflexive pair, the retriggered check must target the succeeded
pair, and not the valid discovered pair.
Differential Revision: https://phabricator.freedesktop.org/D879
commit 72ee528f7fdf82fb1a44958c18a0d4d5055d2d1a
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 12 13:25:16 2016 +0200
conncheck: link succeeded and discovered pairs
When the agent has the role of the stun server, is in controlled mode,
and receives a pair with the "use-candidate" attribute set, it must find
a matching succeded or discovered pair in its conncheck list. This is
described in ICE spec 7.2.1.5, "Updating the Nominated Flag", item #1.
When a matching pair is in succeeded state, the agent must nominate the
valid pair (a discovered pair) constructed from section 7.1.3.2.2,
that's been created from this succeeded one. To make this lookup, we
introduce a new "discovered_pair" member of the CandidateCheckPair
struct, that links the succeeded pair, and its discovered pair
if any.
Differential Revision: https://phabricator.freedesktop.org/D878
commit 2fd7808419f459d5f6e97701ca6a350ddee6b7f2
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 19 17:59:27 2016 +0200
conncheck: improve triggered check of in-progress pairs
This patch update the way triggered checks of in-progress pairs are
handled, according to ICE spec, section 7.2.1.4. Previously the same
connection check was retransmitted with an updated timeout. This causes
problems when a controlling role switch occurs in this time frame.
This is the reason why a new connection check must be generated
reflecting the updated role. We introduce a new flag "recheck_on_timeout"
in the pair indicating that the pair must be rechecked at the next timer
expiration.
Differential Revision: https://phabricator.freedesktop.org/D875
commit ead3453d04fc70865d176ab073636f8b9078cbbc
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 12 13:20:38 2016 +0200
conncheck: invoke the debug dump in more places
Differential Revision: https://phabricator.freedesktop.org/D1123
commit 15c0546f624113b8c0546a1f883a48bff7020f1b
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 19 17:06:32 2016 +0200
conncheck: improve the selection of the pairs to be checked
This patch aims to implement more closely the algorithm described
in RFC 5245 indicating how pairs are transitionned from state Frozen
to Waiting. This is described in 7.1.3.2 when a check succeeded, and
correspond to modifications in function priv_conn_check_unfreeze_related().
This is also described in 5.7.4 when defining the initial state of the
pairs in a conncheck, and correspond to modifications in function
priv_conn_check_unfreeze_next().
This patch introduces the notion of active and frozen check list. It
allows us to define the timer restranmission delay as described in 16.1.
Another modification in priv_conn_check_tick_unlocked() is that every
stream in handled consecutively, and in an independant way. The pacing
was previously of a single STUN request emitted per callback, it is now
of a triggered check per callback OR a single STUN per callback AND per
stream per callback.
The description of ordinary checks per stream in 5.8 is detailled in
function priv_conn_check_tick_stream(), and a remaining of the code
used to nominate a pair by the controlling agent is put in a dedicated
function priv_conn_check_tick_stream_nominate()
Differential Revision: https://phabricator.freedesktop.org/D813
commit 58d061df8f5425dc1add9c6030a2f891ebda4616
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Mon Mar 7 16:35:09 2016 +0100
conncheck: update pair valid property selectively
With this patch, we fix a corner case when the succeeded pair is a
peer-reflexive candidate pair, that already has been discovered
previously, In this case, the current pair -p- should not be marked
valid, because the valid flag is already set on the discovered pair.
Differential Revision: https://phabricator.freedesktop.org/D1124
commit 0636f9addc041cf93c4ff4eaa351b1768d48a32e
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 19 13:12:48 2016 +0200
conncheck: implement ice regular nomination method
This patch implements Regular Nomation as described in RFC5245
8.1.1.1. The controlling agent lets valid pairs accumulate, and
decides which pair to recheck with the use-candidate attribute set.
priv_mark_pair_nominated() follows 7.2.1.5, to update the nominated
pair when acting as a STUN server, and
priv_map_reply_to_conn_check_request() implements 7.1.3.2.4 to
update the nominated pair when acting as a STUN client. A new
property is also added to the agent to control the nomination
mode, which can be regular of aggressive, with default value
set to aggressive.
Two new flags are introduced in the CandidateCheckPair structure:
- use_candidate_on_next_check indicates the STUN client to add the
use-candidate attribute when the pair will be checked. At this
time, the nominated flag has not been set on this pair yet.
- mark_nominated_on_response_arrival indicates the STUN server
to nominate the pair when its succesfull response to a
previous triggered check will arrive (7.2.1.5, item #2)
Differential Revision: https://phabricator.freedesktop.org/D811
commit a602ff57aae6a6afdeab843954c48e6fb5d82d31
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 12 13:02:45 2016 +0200
conncheck: fix pair state transition when successful response is received
According the ICE RFC 5245, 7.1.3.2.3, the pair that *generated* a
successful check should go to state succeeded, not only the valid
pair built in section 7.1.3.2.2.
Differential Revision: https://phabricator.freedesktop.org/D810
commit 3a58ba6120b188d78c5709e0349c0346bfa21c1a
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Mon Feb 1 11:10:21 2016 +0100
conncheck: peer reflexive candidates are not paired
This patch makes the code compliant with ICE RFC, 7.2.1.3 "Learning
Peer Reflexive Candidates" and 7.1.3.2.1 "Discovering Peer Reflexive
Candidates", where discovered candidates do not cause the creation
of new pairs to be checked.
Differential Revision: https://phabricator.freedesktop.org/D805
commit 7a2c1edf502849a868b6f1026e8e2c343dee4ded
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Mon Jun 6 22:24:50 2016 +0200
conncheck: update selected pair when nominated flag is set
This modifies commit 8f1f615. It is better focused to update the
selected pair just after its nominated flag has been set. We also keep
the code homogeneous with other places, where the call to
priv_update_selected_pair() immediately follows the setting of
pair->nominated. Moreover in priv_update_check_list_state_for_ready(),
we would call priv_update_selected_pair() more times that necessary when
iterating on all nominated pairs.
Differential Revision: https://phabricator.freedesktop.org/D1125
commit 8bb210c5af4bcaf342d7fa4fef6034269e976532
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Thu Jun 9 23:28:43 2016 +0200
stun timer: make properties for stun timer tunables
Three STUN binding request properties should be customisable. RFC 5245
describes the retransmission timer of the STUN transaction 'RTO', and
RFC 5389 describes the number of retransmissions to send until a
response is received 'Rc'. The third property is the 'RTO' when
a reliable connection is used.
RFC 5389 introduces a supplementary property 'Rm' as a multiplier used
to compute the final timeout RTO * Rm. However, this property is not
added in libnice, because this would require breaking the public API for
STUN. Currently, our STUN implementation hardcodes a division by two for
this final timeout.
Differential Revision: https://phabricator.freedesktop.org/D1109
commit 80c613699786567fd93db74377138600794a86e0
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Thu Jun 8 16:34:21 2017 -0400
agent: Use base_addr to generate rport in SDP
Reported by Capricornus (zhushengliang)
https://phabricator.freedesktop.org/T7763
commit b4abda09c79e4ce372a3771300abf568c85c7ff5
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Thu Apr 21 18:18:59 2016 +0200
interfaces: ignore predefined network interfaces
Some interfaces, like the one managed by libvirtd to provide a network
bridge to locally hosted virtual machines, can be completely ignored
when gathering ICE candidates. The motivation for adding this
possibility is that, ignoring them doesn't remove capabilities, and
improves the overall speed of the connection check method, by reducing
the number of pairs to be tested. This patch adds the possibility to
define such interfaces in the configuration script.
Differential Revision: https://phabricator.freedesktop.org/D948
commit d5446a72233eab8501be0b3fb9060c8be3ba034b
Author: Philip Withnall <withnall@endlessm.com>
Date: Mon May 1 08:51:40 2017 +0100
examples: Stop installing the examples
Theres no point in installing them; their benefit is in providing
example code to developers.
Debian doesnt package them; Fedora packages them in a separate
subpackage which will have to disappear.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Reviewed-by: Olivier Crête <olivier.crete@collabora.com>
Differential Revision: https://phabricator.freedesktop.org/D1737
commit 0a2cb0a9b14a5a1a4b01ba68ab2e5a2aa965f342
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Tue Apr 5 21:32:39 2016 +0200
agent: do not create a GSource for UDP TURN socket
With this patch, we don't create a new GSource for udp-turn socket,
because it would duplicate the packets already received on the base UDP
socket, as the underlying GSocket is the same. This is a race condition,
because an UDP packet arriving on the base socket, may randomly be
handled by the GSource callback created for the base socket (udp-bsd) of
the callback created for the udp-turn socket. Moreover this callback
already knows how to parse UDP datagrams received from a known turn
server.
This patch also prevents a subtle bug, when a STUN request is received
directly from a peer, is handled by the udp turn socket. If the agent
already has a valid permission for this remote candidate, established
for another pair, it will happily send the STUN reply through the turn
relay. This generates a source address mismatch on the peer agent, when
it'll receive the STUN response from the turn relay instead of the
initial address the request has been sent to.
Differential Revision: https://phabricator.freedesktop.org/D932
commit f6f704c5e8d2193bc67ba2b697c77694e1698c43
Author: Fabrice Bellet <fabrice@bellet.info>
Date: Thu Jun 9 22:22:33 2016 +0200
stun timer: fix timeout of the last retransmission
According to RFC 5389, section 7.2.1, a special timeout is applied to
the last retransmission (Rm * RTO), with Rm default value of 16, instead
of (64 * RTO), 2^6 when the number of transmissions Rc is set to 7.
As spotted by Olivier Crete, stun_timer_* is a public API, that cannot
be changed, and the initial delay (RTO) is not preserved in the
stun_timer_s struct. So we use a hack that implicitely guess Rm from the
number of transmissions Rc, by generalizing the default value of the
spec for Rm and Rc to other values of Rc passed in stun_timer_start(
According to the spec, with the default value of Rc=7, the last delay
should be (64 * RTO), and it is instead (16 * RTO). So the last delay
can be computed by dividing the penultimate delay by two, instead of
multiplying it by two.
Differential Revision: https://phabricator.freedesktop.org/D1108
commit b0538d8c51f65019867b56a45cf90a70bef38f01
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 11 18:31:21 2017 -0400
agent: Ignore remote candidate of non-accepted types
If we disable ice-tcp or ice-udp, ignore the remote
candidates for those types.
commit f49ab88f957a3a250ef2ada0c0fab4fbbd3e5da8
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 11 16:42:55 2017 -0400
conncheck: Check the controlling state when the req was sent
It was checking when the pair was created, but the role may have
already changed when the request is sent.
commit 8fc22b0034d04cbc222e0637152b1cee2879eef3
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Wed Apr 5 17:43:26 2017 -0400
tests_: Add test to verify that only packets from validated addresses pass
https://phabricator.freedesktop.org/T104
Reviewed-by: Philip Withnall <philip.withnall@collabora.co.uk>
Differential Revision: https://phabricator.freedesktop.org/D1717
commit ffc7fddac42728bac6e4753a17bc52e5e610ae8b
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 4 21:27:39 2017 -0400
agent: Drop packets not from validated addresses
This is required by the WebRTC spec.
Remove test-mainloop as it doesnt even try to do
a negotiation.
https://phabricator.freedesktop.org/T104
Differential Revision: https://phabricator.freedesktop.org/D1716
commit 1e9e28dbc98b4f6a7cf4bda0ca73b5abc2735ddc
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 4 14:41:51 2017 -0400
candidate: Add equality check function
Add a function that can check if two candidates point to the same place.
https://phabricator.freedesktop.org/T104
Reviewed-by: Philip Withnall <philip.withnall@collabora.co.uk>
Differential Revision: https://phabricator.freedesktop.org/D1715
commit 10c557f23f8337f1304fff27bd85d2eb713cb249
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Wed Apr 5 17:01:35 2017 -0400
test-credentials: Fix leak
commit ae6d939e48366b80570d713b83334191b0982e71
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 4 20:34:05 2017 -0400
debug: Use libnice-verbose, not libnice-nice-verbose
commit efc6a9be8cb34c899f0454c32e8a1e62b38df474
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 4 18:42:57 2017 -0400
tests: Use automake test-driver for valgrind
This fixes the valgrind integration with the new test drivers.
commit 4e605885c9dcaeb3ee443ec902c9c9189b19043f
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 4 16:16:46 2017 -0400
agent: Remove impossible case
commit e56b910d2d8b70f5677bbd4be579d5b95aff33ad
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 4 16:16:05 2017 -0400
agent: Separate return from NiceSocket and internal enum
The same variable was used for return values from NiceSocket and
for the internal enum, but 0 and -1 have different meanings in both.
commit cd255bddc7fa0ddae056b5358a22b380c4eefc42
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 4 15:24:43 2017 -0400
udp-turn: Add some const to internal APIs
commit db05e8b0fdc713df93cd6a4c3914e5aee38b2391
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 4 12:30:27 2017 -0400
Make clang-analyzer happy
Various little things, none of which should make a functional difference.
commit 0672758b9621801c8f0d9e3c920370983b267a68
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 4 12:29:29 2017 -0400
agent: Don't set variable that won't be used
It exits the loop immediately, so no point to set the variable.
And it makes the clang static analyzer happy.
commit 0de1657e4d15d4f1911ab1fad84ea23e7013070f
Author: Olivier Crête <olivier.crete@collabora.com>
Date: Tue Apr 4 12:25:50 2017 -0400
conncheck: Use the right test for empty remote_frag
It's now an array, not a pointer, so needs to test to emptyness.
It's a bugfix on the previous commit, 59ce41df
commit 59ce41dfb837adf4222b25490cde2e394384ad15
Author: Miguel París Díaz <mparisdiaz@gmail.com>
Date: Fri Mar 31 20:20:38 2017 -0400
conncheck: consider answer received when remote credentials are set
Consider that the answer is received when remote credentials
are set instead of when a remote candidate is set,
which could not happen or could cause more delay for the
connection establishment.
Ported to git master by Olivier Crête
Differential Revision: https://phabricator.freedesktop.org/D1704
diff --git a/agent/Makefile.am b/agent/Makefile.am
index b585393..915f312 100644
--- a/agent/Makefile.am
+++ b/agent/Makefile.am
@@ -22,6 +22,12 @@ if WINDOWS
AM_CFLAGS += -DWINVER=0x0501 # _WIN32_WINNT_WINXP
endif
+BUILT_SOURCES = \
+ agent-enum-types.h \
+ agent-enum-types.c
+
+CLEANFILES += $(BUILT_SOURCES)
+
noinst_LTLIBRARIES = libagent.la
libagent_la_SOURCES = \
@@ -54,6 +60,23 @@ libagent_la_SOURCES = \
outputstream.c \
$(BUILT_SOURCES)
+agent-enum-types.h: agent.h Makefile
+ $(AM_V_GEN)$(GLIB_MKENUMS) \
+ --fhead "#ifndef __AGENT_ENUM_TYPES_H__\n#define __AGENT_ENUM_TYPES_H__ 1\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
+ --fprod "/* enumerations from \"@filename@\" */\n" \
+ --vhead "GType @enum_name@_get_type (void) G_GNUC_CONST;\n#define NICE_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \
+ --ftail "G_END_DECLS\n\n#endif /* !AGENT_ENUM_TYPES_H */" \
+ $(addprefix $(srcdir)/,agent.h) > $@
+
+agent-enum-types.c: agent.h Makefile agent-enum-types.h
+ $(AM_V_GEN)$(GLIB_MKENUMS) \
+ --fhead "#include <config.h>\n#include <glib-object.h>\n#include \"agent.h\"\n#include \"agent-enum-types.h\"" \
+ --fprod "\n/* enumerations from \"@filename@\" */" \
+ --vhead "GType\n@enum_name@_get_type (void)\n{\n static GType type = 0;\n if (!type) {\n static const G@Type@Value values[] = {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+ --vtail " { 0, NULL, NULL }\n };\n type = g_@type@_register_static (\"@EnumName@\", values);\n }\n return type;\n}\n\n" \
+ $(addprefix $(srcdir)/,agent.h) > $@
+
libagent_la_LIBADD = \
$(top_builddir)/random/libnice-random.la \
$(top_builddir)/socket/libsocket.la \
diff --git a/agent/agent-priv.h b/agent/agent-priv.h
index 4d8c9b8..7269be0 100644
--- a/agent/agent-priv.h
+++ b/agent/agent-priv.h
@@ -106,7 +106,6 @@ 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_TR_MIN 15000 /* timer Tr, msecs (ICE ID-19) */
#define NICE_AGENT_MAX_CONNECTIVITY_CHECKS_DEFAULT 100 /* see spec 5.7.3 (ID-19) */
@@ -114,6 +113,27 @@ nice_input_message_iter_compare (const NiceInputMessageIter *a,
* MTU and estimated typical sizes of ICE STUN packet */
#define MAX_STUN_DATAGRAM_PAYLOAD 1300
+#define NICE_COMPONENT_MAX_VALID_CANDIDATES 50 /* maximum number of validates remote candidates to keep, the number is arbitrary but hopefully large enough */
+
+/* A convenient macro to test if the agent is compatible with RFC5245
+ * or OC2007R2. Specifically these two modes share the support
+ * of the regular or aggressive nomination mode */
+#define NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2(obj) \
+ ((obj)->compatibility == NICE_COMPATIBILITY_RFC5245 || \
+ (obj)->compatibility == NICE_COMPATIBILITY_OC2007R2)
+
+/* A grace period before declaring a component as failed, in msecs. This
+ * delay is added to reduce the chance to see the agent receiving new
+ * stun activity just after the conncheck list has been declared failed,
+ * reactiviting conncheck activity, and causing a (valid) state
+ * transitions like that: connecting -> failed -> connecting ->
+ * connected -> ready.
+ * Such transitions are not buggy per-se, but may break the
+ * test-suite, that counts precisely the number of time each state
+ * has been set, and doesnt expect these transcient failed states.
+ */
+#define NICE_AGENT_MAX_TIMER_GRACE_PERIOD 1000
+
struct _NiceAgent
{
GObject parent; /* gobject pointer */
@@ -126,10 +146,14 @@ struct _NiceAgent
NiceProxyType proxy_type; /* property: Proxy type */
gchar *proxy_username; /* property: Proxy username */
gchar *proxy_password; /* property: Proxy password */
- gboolean controlling_mode; /* property: controlling-mode */
+ gboolean saved_controlling_mode;/* property: controlling-mode */
guint timer_ta; /* property: timer Ta */
guint max_conn_checks; /* property: max connectivity checks */
gboolean force_relay; /* property: force relay */
+ guint stun_max_retransmissions; /* property: stun max retransmissions, Rc */
+ guint stun_initial_timeout; /* property: stun initial timeout, RTO */
+ guint stun_reliable_timeout; /* property: stun reliable timeout */
+ NiceNominationMode nomination_mode; /* property: Nomination mode */
GSList *local_addresses; /* list of NiceAddresses for local
interfaces */
@@ -164,6 +188,10 @@ struct _NiceAgent
guint16 rfc4571_expecting_length;
gboolean use_ice_udp;
gboolean use_ice_tcp;
+
+ guint conncheck_timer_grace_period; /* ongoing delay before timer stop */
+ gboolean controlling_mode; /* controlling mode used by the
+ conncheck */
/* XXX: add pointer to internal data struct for ABI-safe extensions */
};
diff --git a/agent/agent.c b/agent/agent.c
index 555fd16..0773c53 100644
--- a/agent/agent.c
+++ b/agent/agent.c
@@ -73,6 +73,7 @@
#include "interfaces.h"
#include "pseudotcp.h"
+#include "agent-enum-types.h"
/* Maximum size of a UDP packets payload, as the packets length field is 16b
* wide. */
@@ -113,6 +114,10 @@ enum
PROP_BYTESTREAM_TCP,
PROP_KEEPALIVE_CONNCHECK,
PROP_FORCE_RELAY,
+ PROP_STUN_MAX_RETRANSMISSIONS,
+ PROP_STUN_INITIAL_TIMEOUT,
+ PROP_STUN_RELIABLE_TIMEOUT,
+ PROP_NOMINATION_MODE,
};
@@ -400,6 +405,13 @@ nice_agent_class_init (NiceAgentClass *klass)
1, /* not a construct property, ignored */
G_PARAM_READWRITE));
+ /**
+ * NiceAgent:controlling-mode:
+ *
+ * Whether the agent has the controlling role. This property should
+ * be modified before gathering candidates, any modification occuring
+ * later will be hold until ICE is restarted.
+ */
g_object_class_install_property (gobject_class, PROP_CONTROLLING_MODE,
g_param_spec_boolean (
"controlling-mode",
@@ -436,6 +448,24 @@ nice_agent_class_init (NiceAgentClass *klass)
0, /* default set in init */
G_PARAM_READWRITE));
+ /**
+ * NiceAgent:nomination-mode:
+ *
+ * The nomination mode used in the ICE specification for describing
+ * the selection of valid pairs to be used upstream.
+ * <para> See also: #NiceNominationMode </para>
+ *
+ * Since: 0.1.15
+ */
+ g_object_class_install_property (gobject_class, PROP_NOMINATION_MODE,
+ g_param_spec_enum (
+ "nomination-mode",
+ "ICE nomination mode",
+ "Nomination mode used in the ICE specification for describing "
+ "the selection of valid pairs to be used upstream",
+ NICE_TYPE_NOMINATION_MODE, NICE_NOMINATION_MODE_AGGRESSIVE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
/**
* NiceAgent:proxy-ip:
*
@@ -708,6 +738,76 @@ nice_agent_class_init (NiceAgentClass *klass)
FALSE,
G_PARAM_READWRITE));
+ /**
+ * NiceAgent:stun-max-retransmissions
+ *
+ * The maximum number of retransmissions of the STUN binding requests
+ * used in the gathering stage, to find our local candidates, and used
+ * in the connection check stage, to test the validity of each
+ * constructed pair. This property is described as 'Rc' in the RFC
+ * 5389, with a default value of 7. The timeout of each STUN request
+ * is doubled for each retransmission, so the choice of this value has
+ * a direct impact on the time needed to move from the CONNECTED state
+ * to the READY state, and on the time needed to complete the GATHERING
+ * state.
+ *
+ * Since: 0.1.15
+ */
+
+ g_object_class_install_property (gobject_class, PROP_STUN_MAX_RETRANSMISSIONS,
+ g_param_spec_uint (
+ "stun-max-retransmissions",
+ "STUN Max Retransmissions",
+ "Maximum number of STUN binding requests retransmissions "
+ "described as 'Rc' in the STUN specification.",
+ 1, 99,
+ STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ /**
+ * NiceAgent:stun-initial-timeout
+ *
+ * The initial timeout (msecs) of the STUN binding requests
+ * used in the gathering stage, to find our local candidates.
+ * This property is described as 'RTO' in the RFC 5389 and RFC 5245.
+ * This timeout is doubled for each retransmission, until
+ * #NiceAgent:stun-max-retransmissions have been done,
+ * with an exception for the last restransmission, where the timeout is
+ * divided by two instead (RFC 5389 indicates that a customisable
+ * multiplier 'Rm' to 'RTO' should be used).
+ *
+ * Since: 0.1.15
+ */
+
+ g_object_class_install_property (gobject_class, PROP_STUN_INITIAL_TIMEOUT,
+ g_param_spec_uint (
+ "stun-initial-timeout",
+ "STUN Initial Timeout",
+ "STUN timeout in msecs of the initial binding requests used in the "
+ "gathering state, described as 'RTO' in the ICE specification.",
+ 20, 9999,
+ STUN_TIMER_DEFAULT_TIMEOUT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ /**
+ * NiceAgent:stun-reliable-timeout
+ *
+ * The initial timeout of the STUN binding requests used
+ * for a reliable timer.
+ *
+ * Since: 0.1.15
+ */
+
+ g_object_class_install_property (gobject_class, PROP_STUN_RELIABLE_TIMEOUT,
+ g_param_spec_uint (
+ "stun-reliable-timeout",
+ "STUN Reliable Timeout",
+ "STUN timeout in msecs of the initial binding requests used for "
+ "a reliable timer.",
+ 20, 99999,
+ STUN_TIMER_DEFAULT_RELIABLE_TIMEOUT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
/* install signals */
/**
@@ -1013,6 +1113,47 @@ static void priv_generate_tie_breaker (NiceAgent *agent)
nice_rng_generate_bytes (agent->rng, 8, (gchar*)&agent->tie_breaker);
}
+static void
+priv_update_controlling_mode (NiceAgent *agent, gboolean value)
+{
+ gboolean update_controlling_mode;
+ GSList *i, *j;
+
+ agent->saved_controlling_mode = value;
+ /* It is safe to update the agent controlling mode when all
+ * components are still in state disconnected. When we leave
+ * this state, the role must stay under the control of the
+ * conncheck algorithm exclusively, until the conncheck is
+ * eventually restarted. See RFC5245, sect 5.2. Determining Role
+ */
+ if (agent->controlling_mode != agent->saved_controlling_mode) {
+ update_controlling_mode = TRUE;
+ for (i = agent->streams;
+ i && update_controlling_mode; i = i->next) {
+ NiceStream *stream = i->data;
+ for (j = stream->components;
+ j && update_controlling_mode; j = j->next) {
+ NiceComponent *component = j->data;
+ if (component->state > NICE_COMPONENT_STATE_DISCONNECTED)
+ update_controlling_mode = FALSE;
+ }
+ }
+ if (update_controlling_mode) {
+ agent->controlling_mode = agent->saved_controlling_mode;
+ nice_debug ("Agent %p : Property set, changing role to \"%s\".",
+ agent, agent->controlling_mode ? "controlling" : "controlled");
+ } else {
+ nice_debug ("Agent %p : Property set, role switch requested "
+ "but conncheck already started.", agent);
+ nice_debug ("Agent %p : Property set, staying with role \"%s\" "
+ "until restart.", agent,
+ agent->controlling_mode ? "controlling" : "controlled");
+ }
+ } else
+ nice_debug ("Agent %p : Property set, role is already \"%s\".", agent,
+ agent->controlling_mode ? "controlling" : "controlled");
+}
+
static void
nice_agent_init (NiceAgent *agent)
{
@@ -1022,7 +1163,9 @@ nice_agent_init (NiceAgent *agent)
/* set defaults; not construct params, so set here */
agent->stun_server_port = DEFAULT_STUN_PORT;
agent->controlling_mode = TRUE;
+ agent->saved_controlling_mode = TRUE;
agent->max_conn_checks = NICE_AGENT_MAX_CONNECTIVITY_CHECKS_DEFAULT;
+ agent->nomination_mode = NICE_NOMINATION_MODE_AGGRESSIVE;
agent->discovery_list = NULL;
agent->discovery_unsched_items = 0;
@@ -1071,6 +1214,24 @@ nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat)
}
+NICEAPI_EXPORT NiceAgent *
+nice_agent_new_full (GMainContext *ctx,
+ NiceCompatibility compat,
+ NiceAgentOption flags)
+{
+ NiceAgent *agent = g_object_new (NICE_TYPE_AGENT,
+ "compatibility", compat,
+ "main-context", ctx,
+ "reliable", (flags & NICE_AGENT_OPTION_RELIABLE) ? TRUE : FALSE,
+ "nomination-mode", (flags & NICE_AGENT_OPTION_REGULAR_NOMINATION) ?
+ NICE_NOMINATION_MODE_REGULAR : NICE_NOMINATION_MODE_AGGRESSIVE,
+ "full-mode", (flags & NICE_AGENT_OPTION_LITE_MODE) ? FALSE : TRUE,
+ NULL);
+
+ return agent;
+}
+
+
static void
nice_agent_get_property (
GObject *object,
@@ -1101,7 +1262,7 @@ nice_agent_get_property (
break;
case PROP_CONTROLLING_MODE:
- g_value_set_boolean (value, agent->controlling_mode);
+ g_value_set_boolean (value, agent->saved_controlling_mode);
break;
case PROP_FULL_MODE:
@@ -1117,6 +1278,10 @@ nice_agent_get_property (
/* XXX: should we prune the list of already existing checks? */
break;
+ case PROP_NOMINATION_MODE:
+ g_value_set_enum (value, agent->nomination_mode);
+ break;
+
case PROP_PROXY_IP:
g_value_set_string (value, agent->proxy_ip);
break;
@@ -1187,6 +1352,18 @@ nice_agent_get_property (
g_value_set_boolean (value, agent->force_relay);
break;
+ case PROP_STUN_MAX_RETRANSMISSIONS:
+ g_value_set_uint (value, agent->stun_max_retransmissions);
+ break;
+
+ case PROP_STUN_INITIAL_TIMEOUT:
+ g_value_set_uint (value, agent->stun_initial_timeout);
+ break;
+
+ case PROP_STUN_RELIABLE_TIMEOUT:
+ g_value_set_uint (value, agent->stun_reliable_timeout);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
@@ -1294,7 +1471,7 @@ nice_agent_set_property (
break;
case PROP_CONTROLLING_MODE:
- agent->controlling_mode = g_value_get_boolean (value);
+ priv_update_controlling_mode (agent, g_value_get_boolean (value));
break;
case PROP_FULL_MODE:
@@ -1309,6 +1486,10 @@ nice_agent_set_property (
agent->max_conn_checks = g_value_get_uint (value);
break;
+ case PROP_NOMINATION_MODE:
+ agent->nomination_mode = g_value_get_enum (value);
+ break;
+
case PROP_PROXY_IP:
g_free (agent->proxy_ip);
agent->proxy_ip = g_value_dup_string (value);
@@ -1374,6 +1555,18 @@ nice_agent_set_property (
agent->force_relay = g_value_get_boolean (value);
break;
+ case PROP_STUN_MAX_RETRANSMISSIONS:
+ agent->stun_max_retransmissions = g_value_get_uint (value);
+ break;
+
+ case PROP_STUN_INITIAL_TIMEOUT:
+ agent->stun_initial_timeout = g_value_get_uint (value);
+ break;
+
+ case PROP_STUN_RELIABLE_TIMEOUT:
+ agent->stun_reliable_timeout = g_value_get_uint (value);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
@@ -1552,7 +1745,6 @@ pseudo_tcp_socket_recv_messages (PseudoTcpSocket *self,
if (len == 0) {
/* Reached EOS. */
- len = 0;
goto done;
} else if (len < 0 &&
pseudo_tcp_socket_get_error (self) == EWOULDBLOCK) {
@@ -1890,6 +2082,17 @@ void agent_gathering_done (NiceAgent *agent)
for (i = agent->streams; i; i = i->next) {
NiceStream *stream = i->data;
+
+ /* We ignore streams not in gathering state, typically already in
+ * ready state. Such streams may have couples (local,remote)
+ * candidates that have not resulted in the creation a new pair
+ * during a previous conncheck session, and we don't want these new
+ * pairs to be added now, because it would generate unneeded
+ * transition changes for a stream unconcerned by this gathering.
+ */
+ if (!stream->gathering)
+ continue;
+
for (j = stream->components; j; j = j->next) {
NiceComponent *component = j->data;
@@ -3113,6 +3316,13 @@ static gboolean priv_add_remote_candidate (
NiceComponent *component;
NiceCandidate *candidate;
+ if (transport == NICE_CANDIDATE_TRANSPORT_UDP &&
+ !agent->use_ice_udp)
+ return FALSE;
+ if (transport != NICE_CANDIDATE_TRANSPORT_UDP &&
+ !agent->use_ice_tcp)
+ return FALSE;
+
if (!agent_find_component (agent, stream_id, component_id, NULL, &component))
return FALSE;
@@ -3195,6 +3405,19 @@ static gboolean priv_add_remote_candidate (
username, password, priority);
}
+ if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
+ /* note: If there are TCP candidates for a media stream,
+ * a controlling agent MUST use the regular selection algorithm,
+ * RFC 6544, sect 8, "Concluding ICE Processing"
+ */
+ if (agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE &&
+ transport != NICE_CANDIDATE_TRANSPORT_UDP) {
+ nice_debug ("Agent %p : we have TCP candidates, switching back "
+ "to regular nomination mode", agent);
+ agent->nomination_mode = NICE_NOMINATION_MODE_REGULAR;
+ }
+ }
+
if (base_addr)
candidate->base_addr = *base_addr;
@@ -3240,6 +3463,8 @@ nice_agent_set_remote_credentials (
g_strlcpy (stream->remote_ufrag, ufrag, NICE_STREAM_MAX_UFRAG);
g_strlcpy (stream->remote_password, pwd, NICE_STREAM_MAX_PWD);
+ conn_check_remote_credentials_set(agent, stream);
+
ret = TRUE;
goto done;
}
@@ -3342,8 +3567,6 @@ _set_remote_candidates_locked (NiceAgent *agent, NiceStream *stream,
}
}
- conn_check_remote_candidates_set(agent, stream, component);
-
if (added > 0) {
conn_check_schedule_next (agent);
}
@@ -3423,7 +3646,8 @@ agent_recv_message_unlocked (
{
NiceAddress from;
GList *item;
- gint retval;
+ RecvStatus retval;
+ gint sockret;
gboolean is_turn = FALSE;
/* We need an address for packet parsing, below. */
@@ -3484,8 +3708,8 @@ agent_recv_message_unlocked (
local_bufs[i + 1].buffer = message->buffers[i].buffer;
local_bufs[i + 1].size = message->buffers[i].size;
}
- retval = nice_socket_recv_messages (nicesock, &local_message, 1);
- if (retval == 1) {
+ sockret = nice_socket_recv_messages (nicesock, &local_message, 1);
+ if (sockret == 1) {
message->length = ntohs (rfc4571_frame);
}
} else {
@@ -3500,7 +3724,7 @@ agent_recv_message_unlocked (
_priv_set_socket_tos (agent, new_socket, stream->tos);
nice_component_attach_socket (component, new_socket);
}
- retval = 0;
+ sockret = 0;
} else {
/* In the case of a real ICE-TCP connection, we can use the socket as a
* bytestream and do the read here with caching of data being read
@@ -3509,9 +3733,9 @@ agent_recv_message_unlocked (
/* TODO: Support bytestream reads */
message->length = 0;
- retval = 0;
+ sockret = 0;
if (available <= 0) {
- retval = available;
+ sockret = available;
/* If we don't call check_connect_result on an outbound connection,
* then is_connected will always return FALSE. That's why we check
@@ -3524,7 +3748,7 @@ agent_recv_message_unlocked (
* not connected, it means that it failed to connect, so we must
* return an error to make the socket fail/closed
*/
- retval = -1;
+ sockret = -1;
} else {
gint flags = G_SOCKET_MSG_PEEK;
@@ -3537,7 +3761,7 @@ agent_recv_message_unlocked (
*/
if (g_socket_receive_message (nicesock->fileno, NULL,
NULL, 0, NULL, NULL, &flags, NULL, NULL) == 0)
- retval = -1;
+ sockret = -1;
}
} else if (agent->rfc4571_expecting_length == 0) {
if ((gsize) available >= sizeof(guint16)) {
@@ -3545,8 +3769,8 @@ agent_recv_message_unlocked (
GInputVector local_buf = { &rfc4571_frame, sizeof(guint16)};
NiceInputMessage local_message = { &local_buf, 1, message->from, 0};
- retval = nice_socket_recv_messages (nicesock, &local_message, 1);
- if (retval == 1) {
+ sockret = nice_socket_recv_messages (nicesock, &local_message, 1);
+ if (sockret == 1) {
agent->rfc4571_expecting_length = ntohs (rfc4571_frame);
available = g_socket_get_available_bytes (nicesock->fileno);
}
@@ -3590,8 +3814,8 @@ agent_recv_message_unlocked (
off += local_bufs[i].size;
}
}
- retval = nice_socket_recv_messages (nicesock, &local_message, 1);
- if (retval == 1) {
+ sockret = nice_socket_recv_messages (nicesock, &local_message, 1);
+ if (sockret == 1) {
message->length = local_message.length;
agent->rfc4571_expecting_length -= local_message.length;
}
@@ -3599,23 +3823,26 @@ agent_recv_message_unlocked (
}
}
} else {
- retval = nice_socket_recv_messages (nicesock, message, 1);
+ sockret = nice_socket_recv_messages (nicesock, message, 1);
}
- if (retval == 0) {
+ if (sockret == 0) {
retval = RECV_WOULD_BLOCK; /* EWOULDBLOCK */
nice_debug_verbose ("%s: Agent %p: no message available on read attempt",
G_STRFUNC, agent);
goto done;
- } else if (retval < 0) {
+ } else if (sockret < 0) {
nice_debug ("Agent %p: %s returned %d, errno (%d) : %s",
- agent, G_STRFUNC, retval, errno, g_strerror (errno));
+ agent, G_STRFUNC, sockret, errno, g_strerror (errno));
retval = RECV_ERROR;
goto done;
+ } else {
+ retval = sockret;
}
- if (retval == RECV_OOB || message->length == 0) {
+ g_assert (retval != RECV_OOB);
+ if (message->length == 0) {
retval = RECV_OOB;
nice_debug_verbose ("%s: Agent %p: message handled out-of-band", G_STRFUNC,
agent);
@@ -3679,8 +3906,6 @@ agent_recv_message_unlocked (
if (retval == RECV_OOB)
goto done;
- agent->media_after_tick = TRUE;
-
/* If the messages stated length is equal to its actual length, its probably
* a STUN message; otherwise its probably data. */
if (stun_message_validate_buffer_length_fast (
@@ -3712,6 +3937,7 @@ agent_recv_message_unlocked (
nice_debug ("%s: Valid STUN packet received.", G_STRFUNC);
retval = RECV_OOB;
g_free (big_buf);
+ agent->media_after_tick = TRUE;
goto done;
}
}
@@ -3722,6 +3948,23 @@ agent_recv_message_unlocked (
g_free (big_buf);
}
+ if (!nice_component_verify_remote_candidate (component,
+ message->from, nicesock)) {
+ if (nice_debug_is_verbose ()) {
+ gchar str[INET6_ADDRSTRLEN];
+
+ nice_address_to_string (message->from, str);
+ nice_debug_verbose ("Agent %p : %d:%d DROPPING packet from unknown source"
+ " %s:%d sock-type: %d", agent, stream->id, component->id, str,
+ nice_address_get_port (message->from), nicesock->type);
+ }
+
+ retval = RECV_OOB;
+ goto done;
+ }
+
+ agent->media_after_tick = TRUE;
+
/* Unhandled STUN; try handling TCP data, then pass to the client. */
if (message->length > 0 && agent->reliable) {
if (!nice_socket_is_reliable (nicesock) &&
@@ -4085,7 +4328,10 @@ static gboolean
nice_agent_recv_cancelled_cb (GCancellable *cancellable, gpointer user_data)
{
GError **error = user_data;
- return !g_cancellable_set_error_if_cancelled (cancellable, error);
+
+ if (error && !*error)
+ g_cancellable_set_error_if_cancelled (cancellable, error);
+ return G_SOURCE_REMOVE;
}
static gint
@@ -4245,7 +4491,6 @@ nice_agent_recv_messages_blocking_or_nonblocking (NiceAgent *agent,
"Component removed during call.");
component = NULL;
- error_reported = TRUE;
goto recv_error;
}
@@ -4734,6 +4979,11 @@ nice_agent_restart (
/* step: regenerate tie-breaker value */
priv_generate_tie_breaker (agent);
+ /* step: reset controlling mode from the property value */
+ agent->controlling_mode = agent->saved_controlling_mode;
+ nice_debug ("Agent %p : ICE restart, reset role to \"%s\".",
+ agent, agent->controlling_mode ? "controlling" : "controlled");
+
for (i = agent->streams; i; i = i->next) {
NiceStream *stream = i->data;
@@ -5665,7 +5915,7 @@ _generate_candidate_sdp (NiceAgent *agent,
g_string_append_printf (sdp, " typ %s", _cand_type_to_sdp (candidate->type));
if (nice_address_is_valid (&candidate->base_addr) &&
!nice_address_equal (&candidate->addr, &candidate->base_addr)) {
- port = nice_address_get_port (&candidate->addr);
+ port = nice_address_get_port (&candidate->base_addr);
nice_address_to_string (&candidate->base_addr, ip4);
g_string_append_printf (sdp, " raddr %s rport %d", ip4,
port == 0 ? 9 : port);
diff --git a/agent/agent.h b/agent/agent.h
index 47c4d5a..520c4c5 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -377,6 +377,45 @@ typedef enum
NICE_PROXY_TYPE_LAST = NICE_PROXY_TYPE_HTTP,
} NiceProxyType;
+/**
+ * NiceNominationMode:
+ * @NICE_NOMINATION_MODE_AGGRESSIVE: Aggressive nomination mode
+ * @NICE_NOMINATION_MODE_REGULAR: Regular nomination mode
+ *
+ * An enum to specity the kind of nomination mode to use by
+ * the agent, as described in RFC 5245. Two modes exists,
+ * regular and aggressive. They differ by the way the controlling
+ * agent chooses to put the USE-CANDIDATE attribute in its STUN
+ * messages. The aggressive mode is supposed to nominate a pair
+ * faster, than the regular mode, potentially causing the nominated
+ * pair to change until the connection check completes.
+ *
+ * Since: 0.1.15
+ */
+typedef enum
+{
+ NICE_NOMINATION_MODE_REGULAR = 0,
+ NICE_NOMINATION_MODE_AGGRESSIVE,
+} NiceNominationMode;
+
+/**
+ * NiceAgentOption:
+ * @NICE_AGENT_OPTION_REGULAR_NOMINATION: Enables regular nomination, default
+ * is aggrssive mode (see #NiceNominationMode).
+ * @NICE_AGENT_OPTION_RELIABLE: Enables reliable mode, possibly using PseudoTCP, * see nice_agent_new_reliable().
+ * @NICE_AGENT_OPTION_LITE_MODE: Enable lite mode
+ *
+ * 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
+ * the other value.
+ *
+ * Since: 0.1.15
+ */
+typedef enum {
+ NICE_AGENT_OPTION_REGULAR_NOMINATION = 1 << 0,
+ NICE_AGENT_OPTION_RELIABLE = 1 << 1,
+ NICE_AGENT_OPTION_LITE_MODE = 1 << 2,
+} NiceAgentOption;
/**
* NiceAgentRecvFunc:
@@ -428,6 +467,26 @@ nice_agent_new (GMainContext *ctx, NiceCompatibility compat);
NiceAgent *
nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat);
+/**
+ * nice_agent_new_full:
+ * @ctx: The Glib Mainloop Context to use for timers
+ * @compat: The compatibility mode of the agent
+ * @flags: Flags to set the properties
+ *
+ * Create a new #NiceAgent with parameters that must be be defined at
+ * construction time.
+ * The returned object must be freed with g_object_unref()
+ * <para> See also: #NiceNominationMode and #NiceAgentOption</para>
+ *
+ * Since: 0.1.15
+ *
+ * Returns: The new agent GObject
+ */
+NiceAgent *
+nice_agent_new_full (GMainContext *ctx,
+ NiceCompatibility compat,
+ NiceAgentOption flags);
+
/**
* nice_agent_add_local_address:
* @agent: The #NiceAgent Object
@@ -447,7 +506,6 @@ nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat);
gboolean
nice_agent_add_local_address (NiceAgent *agent, NiceAddress *addr);
-
/**
* nice_agent_add_stream:
* @agent: The #NiceAgent Object
diff --git a/agent/candidate.c b/agent/candidate.c
index 27966ef..85f8f65 100644
--- a/agent/candidate.c
+++ b/agent/candidate.c
@@ -360,3 +360,14 @@ nice_candidate_copy (const NiceCandidate *candidate)
return copy;
}
+
+NICEAPI_EXPORT gboolean
+nice_candidate_equal_target (const NiceCandidate *candidate1,
+ const NiceCandidate *candidate2)
+{
+ g_return_val_if_fail (candidate1 != NULL, FALSE);
+ g_return_val_if_fail (candidate2 != NULL, FALSE);
+
+ return (candidate1->transport == candidate2->transport &&
+ nice_address_equal (&candidate1->addr, &candidate2->addr));
+}
diff --git a/agent/candidate.h b/agent/candidate.h
index fadfce3..e556c16 100644
--- a/agent/candidate.h
+++ b/agent/candidate.h
@@ -230,7 +230,23 @@ nice_candidate_free (NiceCandidate *candidate);
NiceCandidate *
nice_candidate_copy (const NiceCandidate *candidate);
-GType nice_candidate_get_type (void);
+/**
+ * nice_candidate_equal_target:
+ * @candidate1: A candidate
+ * @candidate2: A candidate
+ *
+ * Verifies that the candidates point to the same place, meaning they have
+ * the same transport and the same address. It ignores all other aspects.
+ *
+ * Returns: %TRUE if the candidates point to the same place
+ *
+ * Since: 0.1.15
+ */
+gboolean
+nice_candidate_equal_target (const NiceCandidate *candidate1,
+ const NiceCandidate *candidate2);
+
+ GType nice_candidate_get_type (void);
/**
* NICE_TYPE_CANDIDATE:
diff --git a/agent/component.c b/agent/component.c
index 32f7463..6eee90e 100644
--- a/agent/component.c
+++ b/agent/component.c
@@ -105,6 +105,13 @@ socket_source_attach (SocketSource *socket_source, GMainContext *context)
if (socket_source->socket->fileno == NULL)
return;
+ /* Do not create a GSource for UDP turn socket, because it
+ * would duplicate the packets already received on the base
+ * UDP socket.
+ */
+ if (socket_source->socket->type == NICE_SOCKET_TYPE_UDP_TURN)
+ return;
+
/* Create a source. */
source = g_socket_create_source (socket_source->socket->fileno,
G_IO_IN, NULL);
@@ -380,7 +387,7 @@ nice_component_restart (NiceComponent *cmp)
for (i = cmp->remote_candidates; i; i = i->next) {
NiceCandidate *candidate = i->data;
- /* note: do not remove the local candidate that is
+ /* note: do not remove the remote candidate that is
* currently part of the 'selected pair', see ICE
* 9.1.1.1. "ICE Restarts" (ID-19) */
if (candidate == cmp->selected_pair.remote) {
@@ -435,6 +442,8 @@ nice_component_update_selected_pair (NiceComponent *component, const CandidatePa
component->selected_pair.remote = pair->remote;
component->selected_pair.priority = pair->priority;
component->selected_pair.prflx_priority = pair->prflx_priority;
+
+ nice_component_add_valid_candidate (component, pair->remote);
}
/*
@@ -514,6 +523,11 @@ nice_component_set_selected_remote_candidate (NiceComponent *component,
component->selected_pair.remote = remote;
component->selected_pair.priority = priority;
+ /* 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.
+ */
+ component->fallback_mode = TRUE;
+
return local;
}
@@ -991,6 +1005,18 @@ nice_component_class_init (NiceComponentClass *klass)
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}
+static gboolean
+dummy_callback (gpointer data)
+{
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+source_set_dummy_callback (GSource *source)
+{
+ g_source_set_callback (source, dummy_callback, NULL, NULL);
+}
+
static void
nice_component_init (NiceComponent *component)
{
@@ -1013,7 +1039,7 @@ nice_component_init (NiceComponent *component)
component->stop_cancellable = g_cancellable_new ();
component->stop_cancellable_source =
g_cancellable_source_new (component->stop_cancellable);
- g_source_set_dummy_callback (component->stop_cancellable_source);
+ source_set_dummy_callback (component->stop_cancellable_source);
g_source_attach (component->stop_cancellable_source, component->own_ctx);
component->ctx = g_main_context_ref (component->own_ctx);
@@ -1107,6 +1133,9 @@ nice_component_finalize (GObject *obj)
g_warn_if_fail (cmp->remote_candidates == NULL);
g_warn_if_fail (cmp->incoming_checks == NULL);
+ g_list_free_full (cmp->valid_candidates,
+ (GDestroyNotify) nice_candidate_free);
+
g_clear_object (&cmp->tcp);
g_clear_object (&cmp->stop_cancellable);
g_clear_object (&cmp->iostream);
@@ -1225,7 +1254,7 @@ component_source_prepare (GSource *source, gint *timeout_)
child_socket_source->source =
g_socket_create_source (child_socket_source->socket->fileno, G_IO_IN,
NULL);
- g_source_set_dummy_callback (child_socket_source->source);
+ source_set_dummy_callback (child_socket_source->source);
g_source_add_child_source (source, child_socket_source->source);
g_source_unref (child_socket_source->source);
component_source->socket_sources =
@@ -1370,7 +1399,7 @@ nice_component_input_source_new (NiceAgent *agent, guint stream_id,
GSource *cancellable_source;
cancellable_source = g_cancellable_source_new (cancellable);
- g_source_set_dummy_callback (cancellable_source);
+ source_set_dummy_callback (cancellable_source);
g_source_add_child_source ((GSource *) component_source,
cancellable_source);
g_source_unref (cancellable_source);
@@ -1421,3 +1450,83 @@ turn_server_unref (TurnServer *turn)
g_slice_free (TurnServer, turn);
}
}
+
+void
+nice_component_add_valid_candidate (NiceComponent *component,
+ const NiceCandidate *candidate)
+{
+ guint count = 0;
+ GList *item, *last = NULL;
+
+ for (item = component->valid_candidates; item; item = item->next) {
+ NiceCandidate *cand = item->data;
+
+ last = item;
+ count++;
+ if (nice_candidate_equal_target (cand, candidate))
+ return;
+ }
+
+ /* New candidate */
+
+ if (nice_debug_is_enabled ()) {
+ char str[INET6_ADDRSTRLEN];
+ nice_address_to_string (&candidate->addr, str);
+ nice_debug ("Agent %p : %d:%d Adding valid source"
+ " candidate: %s:%d trans: %d", component->agent,
+ candidate->stream_id, candidate->component_id, str,
+ nice_address_get_port (&candidate->addr), candidate->transport);
+ }
+
+ component->valid_candidates = g_list_prepend (
+ component->valid_candidates, nice_candidate_copy (candidate));
+
+ /* Delete the last one to make sure we don't have a list that is too long,
+ * the candidates are not freed on ICE restart as this would be more complex,
+ * we just keep the list not too long.
+ */
+ if (count > NICE_COMPONENT_MAX_VALID_CANDIDATES) {
+ NiceCandidate *cand = last->data;
+
+ component->valid_candidates = g_list_delete_link (
+ component->valid_candidates, last);
+ nice_candidate_free (cand);
+ }
+}
+
+gboolean
+nice_component_verify_remote_candidate (NiceComponent *component,
+ const NiceAddress *address, NiceSocket *nicesock)
+{
+ GList *item;
+
+ if (component->fallback_mode)
+ return TRUE;
+
+ for (item = component->valid_candidates; item; item = item->next) {
+ NiceCandidate *cand = item->data;
+
+ if (((nicesock->type == NICE_SOCKET_TYPE_TCP_BSD &&
+ (cand->transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE ||
+ cand->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE ||
+ cand->transport == NICE_CANDIDATE_TRANSPORT_TCP_SO)) ||
+ cand->transport == NICE_CANDIDATE_TRANSPORT_UDP) &&
+ nice_address_equal (address, &cand->addr)) {
+ /* fast return if it's already the first */
+ if (item == component->valid_candidates)
+ return TRUE;
+
+ /* Put the current candidate at the top so that in the normal use-case,
+ * this function becomes O(1).
+ */
+ component->valid_candidates = g_list_remove_link (
+ component->valid_candidates, item);
+ component->valid_candidates = g_list_concat (item,
+ component->valid_candidates);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/agent/component.h b/agent/component.h
index 6712794..a8a1222 100644
--- a/agent/component.h
+++ b/agent/component.h
@@ -159,12 +159,14 @@ struct _NiceComponent {
NiceComponentState state;
GSList *local_candidates; /* list of NiceCandidate objs */
GSList *remote_candidates; /* list of NiceCandidate objs */
+ GList *valid_candidates; /* list of owned remote NiceCandidates that are part of valid pairs */
GSList *socket_sources; /* list of SocketSource objs; must only grow monotonically */
guint socket_sources_age; /* incremented when socket_sources changes */
GSList *incoming_checks; /* list of IncomingCheck objs */
GList *turn_servers; /* List of TurnServer objs */
CandidatePair selected_pair; /* independent from checklists,
see ICE 11.1. "Sending Media" (ID-19) */
+ gboolean fallback_mode; /* in this case, accepts packets from all, ignore candidate validation */
NiceCandidate *restart_candidate; /* for storing active remote candidate during a restart */
NiceCandidate *turn_candidate; /* for storing active turn candidate if turn servers have been cleared */
/* I/O handling. The main context must always be non-NULL, and is used for all
@@ -301,6 +303,14 @@ turn_server_ref (TurnServer *turn);
void
turn_server_unref (TurnServer *turn);
+void
+nice_component_add_valid_candidate (NiceComponent *component,
+ const NiceCandidate *candidate);
+
+gboolean
+nice_component_verify_remote_candidate (NiceComponent *component,
+ const NiceAddress *address, NiceSocket *nicesock);
+
G_END_DECLS
diff --git a/agent/conncheck.c b/agent/conncheck.c
index dda2f2f..c8a4edf 100644
--- a/agent/conncheck.c
+++ b/agent/conncheck.c
@@ -64,8 +64,8 @@
static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStream *stream);
static void priv_update_check_list_state_for_ready (NiceAgent *agent, NiceStream *stream, NiceComponent *component);
-static guint priv_prune_pending_checks (NiceStream *stream, guint component_id);
-static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *local_socket, NiceCandidate *remote_cand, gboolean use_candidate);
+static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, guint component_id);
+static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *local_socket, NiceCandidate *remote_cand);
static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceCandidate *localcand, NiceCandidate *remotecand);
static size_t priv_create_username (NiceAgent *agent, NiceStream *stream,
guint component_id, NiceCandidate *remote, NiceCandidate *local,
@@ -76,6 +76,8 @@ static void conn_check_free_item (gpointer data);
static CandidateCheckPair *priv_conn_check_add_for_candidate_pair_matched (
NiceAgent *agent, guint stream_id, NiceComponent *component,
NiceCandidate *local, NiceCandidate *remote, NiceCheckState initial_state);
+static gboolean priv_update_selected_pair (NiceAgent *agent,
+ NiceComponent *component, CandidateCheckPair *pair);
static int priv_timer_expired (GTimeVal *timer, GTimeVal *now)
{
@@ -84,6 +86,17 @@ static int priv_timer_expired (GTimeVal *timer, GTimeVal *now)
now->tv_sec >= timer->tv_sec;
}
+static unsigned int priv_timer_remainder (GTimeVal *timer, GTimeVal *now)
+{
+ unsigned int delay;
+ if (now->tv_sec > timer->tv_sec ||
+ (now->tv_sec == timer->tv_sec && now->tv_usec > timer->tv_usec))
+ return 0;
+ delay = (timer->tv_sec - now->tv_sec) * 1000;
+ delay += ((signed long)(timer->tv_usec - now->tv_usec)) / 1000;
+ return delay;
+}
+
static gchar
priv_state_to_gchar (NiceCheckState state)
{
@@ -98,8 +111,6 @@ priv_state_to_gchar (NiceCheckState state)
return 'F';
case NICE_CHECK_FROZEN:
return 'Z';
- case NICE_CHECK_CANCELLED:
- return 'C';
case NICE_CHECK_DISCOVERED:
return 'D';
default:
@@ -107,6 +118,54 @@ priv_state_to_gchar (NiceCheckState state)
}
}
+static const gchar *
+priv_state_to_string (NiceCheckState state)
+{
+ switch (state) {
+ case NICE_CHECK_WAITING:
+ return "waiting";
+ case NICE_CHECK_IN_PROGRESS:
+ return "in progress";
+ case NICE_CHECK_SUCCEEDED:
+ return "succeeded";
+ case NICE_CHECK_FAILED:
+ return "failed";
+ case NICE_CHECK_FROZEN:
+ return "frozen";
+ case NICE_CHECK_DISCOVERED:
+ return "discovered";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static const gchar *
+priv_ice_return_to_string (StunUsageIceReturn ice_return)
+{
+ switch (ice_return) {
+ case STUN_USAGE_ICE_RETURN_SUCCESS:
+ return "success";
+ case STUN_USAGE_ICE_RETURN_ERROR:
+ return "error";
+ case STUN_USAGE_ICE_RETURN_INVALID:
+ return "invalid";
+ case STUN_USAGE_ICE_RETURN_ROLE_CONFLICT:
+ return "role conflict";
+ case STUN_USAGE_ICE_RETURN_INVALID_REQUEST:
+ return "invalid request";
+ case STUN_USAGE_ICE_RETURN_INVALID_METHOD:
+ return "invalid method";
+ case STUN_USAGE_ICE_RETURN_MEMORY_ERROR:
+ return "memory error";
+ case STUN_USAGE_ICE_RETURN_INVALID_ADDRESS:
+ return "invalid address";
+ case STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS:
+ return "no mapped address";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
static const gchar *
priv_candidate_type_to_string (NiceCandidateType type)
{
@@ -130,12 +189,15 @@ priv_candidate_type_to_string (NiceCandidateType type)
static void
priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar *detail)
{
- GSList *i, *k;
- guint j;
+ GSList *i, *k, *l;
+ guint j, m;
+ GTimeVal now;
if (!nice_debug_is_verbose ())
return;
+ g_get_current_time (&now);
+
#define PRIORITY_LEN 32
nice_debug ("Agent %p : *** conncheck list DUMP (called from %s%s)",
@@ -148,26 +210,34 @@ priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar *
if (pair->component_id == j) {
gchar local_addr[INET6_ADDRSTRLEN];
gchar remote_addr[INET6_ADDRSTRLEN];
- StunTimer *timer = &pair->timer;
nice_address_to_string (&pair->local->addr, local_addr);
nice_address_to_string (&pair->remote->addr, remote_addr);
nice_debug ("Agent %p : *** sc=%d/%d : pair %p : "
- "f=%s t=%s:%s timer=%d/%d %d/%dms "
- "[%s]:%u > [%s]:%u state=%c%s%s%s",
+ "f=%s t=%s:%s [%s]:%u > [%s]:%u state=%c%s%s%s",
agent, pair->stream_id, pair->component_id, pair,
pair->foundation,
priv_candidate_type_to_string (pair->local->type),
priv_candidate_type_to_string (pair->remote->type),
- timer->retransmissions, timer->max_retransmissions,
- timer->delay - stun_timer_remainder (timer), timer->delay,
local_addr, nice_address_get_port (&pair->local->addr),
remote_addr, nice_address_get_port (&pair->remote->addr),
priv_state_to_gchar (pair->state),
pair->valid ? "V" : "",
pair->nominated ? "N" : "",
g_slist_find (agent->triggered_check_queue, pair) ? "T" : "");
+
+ for (l = pair->stun_transactions, m = 0; l; l = l->next, m++) {
+ StunTransaction *stun = l->data;
+ nice_debug ("Agent %p : *** sc=%d/%d : pair %p : "
+ "stun#=%d timer=%d/%d %d/%dms buf=%p %s",
+ agent, pair->stream_id, pair->component_id, pair, m,
+ stun->timer.retransmissions, stun->timer.max_retransmissions,
+ stun->timer.delay - priv_timer_remainder (&stun->next_tick, &now),
+ stun->timer.delay,
+ stun->message.buffer,
+ (m == 0 && pair->retransmit) ? "(R)" : "");
+ }
}
}
}
@@ -181,6 +251,8 @@ priv_add_pair_to_triggered_check_queue (NiceAgent *agent, CandidateCheckPair *pa
{
g_assert (pair);
+ pair->state = NICE_CHECK_IN_PROGRESS;
+ nice_debug ("Agent %p : pair %p state IN_PROGRESS", agent, pair);
if (agent->triggered_check_queue == NULL ||
g_slist_find (agent->triggered_check_queue, pair) == NULL)
agent->triggered_check_queue = g_slist_append (agent->triggered_check_queue, pair);
@@ -209,6 +281,89 @@ priv_get_pair_from_triggered_check_queue (NiceAgent *agent)
return pair;
}
+/*
+ * Check if the conncheck list if Active according to
+ * ICE spec, 5.7.4 (Computing States)
+ *
+ * note: the ICE spec in unclear about that, but the check list should
+ * be considered active when there is at least a pair in Waiting state
+ * OR a pair in In-Progress state.
+ */
+static gboolean
+priv_is_checklist_active (NiceStream *stream)
+{
+ GSList *i;
+
+ for (i = stream->conncheck_list; i ; i = i->next) {
+ CandidateCheckPair *p = i->data;
+ if (p->state == NICE_CHECK_WAITING || p->state == NICE_CHECK_IN_PROGRESS)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*
+ * Check if the conncheck list if Frozen according to
+ * ICE spec, 5.7.4 (Computing States)
+ */
+static gboolean
+priv_is_checklist_frozen (NiceStream *stream)
+{
+ GSList *i;
+
+ if (stream->conncheck_list == NULL)
+ return FALSE;
+
+ for (i = stream->conncheck_list; i ; i = i->next) {
+ CandidateCheckPair *p = i->data;
+ if (p->state != NICE_CHECK_FROZEN)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Check if all components of the stream have
+ * a valid pair (used for ICE spec, 7.1.3.2.3, point 2.)
+ */
+static gboolean
+priv_all_components_have_valid_pair (NiceStream *stream)
+{
+ guint i;
+ GSList *j;
+
+ for (i = 1; i <= stream->n_components; i++) {
+ for (j = stream->conncheck_list; j ; j = j->next) {
+ CandidateCheckPair *p = j->data;
+ if (p->component_id == i && p->valid)
+ break;
+ }
+ if (j == NULL)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Check if the foundation in parameter matches the foundation
+ * of a valid pair in the conncheck list [of stream] (used for ICE spec,
+ * 7.1.3.2.3, point 2.)
+ */
+static gboolean
+priv_foundation_matches_a_valid_pair (const gchar *foundation, NiceStream *stream)
+{
+ GSList *i;
+
+ for (i = stream->conncheck_list; i ; i = i->next) {
+ CandidateCheckPair *p = i->data;
+ if (p->valid &&
+ strncmp (p->foundation, foundation,
+ NICE_CANDIDATE_PAIR_MAX_FOUNDATION) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
/*
* Finds the next connectivity check in WAITING state.
*/
@@ -218,7 +373,6 @@ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check
/* note: list is sorted in priority order to first waiting check has
* the highest priority */
-
for (i = conn_check_list; i ; i = i->next) {
CandidateCheckPair *p = i->data;
if (p->state == NICE_CHECK_WAITING)
@@ -228,6 +382,57 @@ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check
return NULL;
}
+/*
+ * Finds the next connectivity check in FROZEN state.
+ */
+static CandidateCheckPair *
+priv_conn_check_find_next_frozen (GSList *conn_check_list)
+{
+ GSList *i;
+
+ /* note: list is sorted in priority order to first frozen check has
+ * the highest priority */
+ for (i = conn_check_list; i ; i = i->next) {
+ CandidateCheckPair *p = i->data;
+ if (p->state == NICE_CHECK_FROZEN)
+ return p;
+ }
+
+ return NULL;
+}
+
+/*
+ * Returns the number of active check lists of the agent
+ */
+static guint
+priv_number_of_active_check_lists (NiceAgent *agent)
+{
+ guint n = 0;
+ GSList *i;
+
+ for (i = agent->streams; i ; i = i->next)
+ if (priv_is_checklist_active (i->data))
+ n++;
+ return n;
+}
+
+/*
+ * Returns the first stream of the agent having a Frozen
+ * connection check list
+ */
+static NiceStream *
+priv_find_first_frozen_check_list (NiceAgent *agent)
+{
+ GSList *i;
+
+ for (i = agent->streams; i ; i = i->next) {
+ NiceStream *stream = i->data;
+ if (priv_is_checklist_frozen (stream))
+ return stream;
+ }
+ return NULL;
+}
+
/*
* Initiates a new connectivity check for a ICE candidate pair.
*
@@ -235,8 +440,6 @@ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check
*/
static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair *pair)
{
- g_get_current_time (&pair->next_tick);
- g_time_val_add (&pair->next_tick, agent->timer_ta * 1000);
pair->state = NICE_CHECK_IN_PROGRESS;
nice_debug ("Agent %p : pair %p state IN_PROGRESS", agent, pair);
conn_check_send (agent, pair);
@@ -246,58 +449,55 @@ static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair *
/*
* Unfreezes the next connectivity check in the list. Follows the
* algorithm (2.) defined in 5.7.4 (Computing States) of the ICE spec
- * (ID-19), with some exceptions (see comments in code).
+ * (RFC5245)
*
* See also sect 7.1.2.2.3 (Updating Pair States), and
* priv_conn_check_unfreeze_related().
*
* @return TRUE on success, and FALSE if no frozen candidates were found.
*/
-static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent)
+static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent, NiceStream *stream)
{
- CandidateCheckPair *pair = NULL;
GSList *i, *j;
-
- /* XXX: the unfreezing is implemented a bit differently than in the
- * current ICE spec, but should still be interoperate:
- * - checks are not grouped by foundation
- * - one frozen check is unfrozen (lowest component-id, highest
- * priority)
- */
+ GSList *found_list = NULL;
+ gboolean result = FALSE;
priv_print_conn_check_lists (agent, G_STRFUNC, NULL);
- for (i = agent->streams; i; i = i->next) {
- NiceStream *stream = i->data;
- guint64 max_frozen_priority = 0;
+ for (i = stream->conncheck_list; i ; i = i->next) {
+ CandidateCheckPair *p1 = i->data;
+ CandidateCheckPair *pair = NULL;
+ guint lowest_component_id = stream->n_components + 1;
+ guint64 highest_priority = 0;
+ if (g_slist_find_custom (found_list, p1->foundation, (GCompareFunc)strcmp))
+ continue;
+ found_list = g_slist_prepend (found_list, p1->foundation);
for (j = stream->conncheck_list; j ; j = j->next) {
- CandidateCheckPair *p = j->data;
-
- /* XXX: the prio check could be removed as the pairs are sorted
- * already */
-
- if (p->state == NICE_CHECK_FROZEN) {
- if (p->priority > max_frozen_priority) {
- max_frozen_priority = p->priority;
- pair = p;
- }
+ CandidateCheckPair *p2 = i->data;
+ if (strncmp (p2->foundation, p1->foundation,
+ NICE_CANDIDATE_PAIR_MAX_FOUNDATION) == 0) {
+ if (p2->component_id < lowest_component_id ||
+ (p2->component_id == lowest_component_id &&
+ p2->priority > highest_priority)) {
+ pair = p2;
+ lowest_component_id = p2->component_id;
+ highest_priority = p2->priority;
+ }
}
}
- if (pair)
- break;
- }
-
- if (pair) {
- nice_debug ("Agent %p : Pair %p with s/c-id %u/%u (%s) unfrozen.", agent, pair, pair->stream_id, pair->component_id, pair->foundation);
- pair->state = NICE_CHECK_WAITING;
- nice_debug ("Agent %p : pair %p state WAITING", agent, pair);
- return TRUE;
+ if (pair) {
+ nice_debug ("Agent %p : Pair %p with s/c-id %u/%u (%s) unfrozen.",
+ agent, pair, pair->stream_id, pair->component_id, pair->foundation);
+ pair->state = NICE_CHECK_WAITING;
+ nice_debug ("Agent %p : pair %p state WAITING", agent, pair);
+ result = TRUE;
+ }
}
-
- return FALSE;
+ g_slist_free (found_list);
+ return result;
}
/*
@@ -314,7 +514,6 @@ static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent)
static void priv_conn_check_unfreeze_related (NiceAgent *agent, NiceStream *stream, CandidateCheckPair *ok_check)
{
GSList *i, *j;
- guint unfrozen = 0;
g_assert (ok_check);
g_assert (ok_check->state == NICE_CHECK_SUCCEEDED);
@@ -334,60 +533,147 @@ static void priv_conn_check_unfreeze_related (NiceAgent *agent, NiceStream *stre
nice_debug ("Agent %p : Unfreezing check %p (after successful check %p).", agent, p, ok_check);
p->state = NICE_CHECK_WAITING;
nice_debug ("Agent %p : pair %p state WAITING", agent, p);
- ++unfrozen;
}
}
}
/* step: perform the step (2) of 'Updating Pair States' */
stream = agent_find_stream (agent, ok_check->stream_id);
- if (nice_stream_all_components_ready (stream)) {
- /* step: unfreeze checks from other streams */
+ if (priv_all_components_have_valid_pair (stream)) {
for (i = agent->streams; i ; i = i->next) {
+ /* the agent examines the check list for each other
+ * media stream in turn
+ */
NiceStream *s = i->data;
- for (j = stream->conncheck_list; j ; j = j->next) {
- CandidateCheckPair *p = j->data;
-
- if (p->stream_id == s->id &&
- p->stream_id != ok_check->stream_id) {
- if (p->state == NICE_CHECK_FROZEN &&
- strcmp (p->foundation, ok_check->foundation) == 0) {
+ if (s->id == ok_check->stream_id)
+ continue;
+ if (priv_is_checklist_active (s)) {
+ /* checklist is Active
+ */
+ for (j = s->conncheck_list; j ; j = j->next) {
+ CandidateCheckPair *p = j->data;
+ if (p->state == NICE_CHECK_FROZEN &&
+ priv_foundation_matches_a_valid_pair (p->foundation, stream)) {
nice_debug ("Agent %p : Unfreezing check %p from stream %u (after successful check %p).", agent, p, s->id, ok_check);
p->state = NICE_CHECK_WAITING;
nice_debug ("Agent %p : pair %p state WAITING", agent, p);
- ++unfrozen;
-
- }
- }
+ }
+ }
+ } else if (priv_is_checklist_frozen (s)) {
+ /* checklist is Frozen
+ */
+ gboolean match_found = FALSE;
+ /* check if there is one pair in the check list whose
+ * foundation matches a pair in the valid list under
+ * consideration
+ */
+ for (j = s->conncheck_list; j ; j = j->next) {
+ CandidateCheckPair *p = j->data;
+ if (priv_foundation_matches_a_valid_pair (p->foundation, stream)) {
+ match_found = TRUE;
+ nice_debug ("Agent %p : Unfreezing check %p from stream %u (after successful check %p).", agent, p, s->id, ok_check);
+ p->state = NICE_CHECK_WAITING;
+ nice_debug ("Agent %p : pair %p state WAITING", agent, p);
+ }
+ }
+
+ if (!match_found) {
+ /* set the pair with the lowest component ID
+ * and highest priority to Waiting
+ */
+ priv_conn_check_unfreeze_next (agent, s);
+ }
}
- /* note: only unfreeze check from one stream at a time */
- if (unfrozen)
- break;
}
}
+}
- if (unfrozen == 0)
- priv_conn_check_unfreeze_next (agent);
+/*
+ * Create a new STUN transaction and add it to the list
+ * of ongoing stun transactions of a pair.
+ *
+ * @pair the pair the new stun transaction should be added to.
+ * @return the created stun transaction.
+ */
+static StunTransaction *
+priv_add_stun_transaction (CandidateCheckPair *pair)
+{
+ StunTransaction *stun = g_slice_new0 (StunTransaction);
+ pair->stun_transactions = g_slist_prepend (pair->stun_transactions, stun);
+ pair->retransmit = TRUE;
+ return stun;
}
+/*
+ * Forget a STUN transaction.
+ *
+ * @data the stun transaction to be forgotten.
+ * @user_data the component contained the concerned stun agent.
+ */
static void
-candidate_check_pair_fail (NiceStream *stream, NiceAgent *agent, CandidateCheckPair *p)
+priv_forget_stun_transaction (gpointer data, gpointer user_data)
{
+ StunTransaction *stun = data;
+ NiceComponent *component = user_data;
StunTransactionId id;
+
+ if (stun->message.buffer != NULL) {
+ stun_message_id (&stun->message, id);
+ stun_agent_forget_transaction (&component->stun_agent, id);
+ }
+}
+
+static void
+priv_free_stun_transaction (gpointer data)
+{
+ g_slice_free (StunTransaction, data);
+}
+
+/*
+ * Remove a STUN transaction from a pair, and forget it
+ * from the related component stun agent.
+ *
+ * @pair the pair the stun transaction should be removed from.
+ * @stun the stun transaction to be removed.
+ * @component the component containing the stun agent used to
+ * forget the stun transaction.
+ */
+static void
+priv_remove_stun_transaction (CandidateCheckPair *pair,
+ StunTransaction *stun, NiceComponent *component)
+{
+ priv_forget_stun_transaction (stun, component);
+ pair->stun_transactions = g_slist_remove (pair->stun_transactions, stun);
+ priv_free_stun_transaction (stun);
+}
+
+/*
+ * Remove all STUN transactions from a pair, and forget them
+ * from the related component stun agent.
+ *
+ * @pair the pair the stun list should be cleared.
+ * @component the component containing the stun agent used to
+ * forget the stun transactions.
+ */
+static void
+priv_free_all_stun_transactions (CandidateCheckPair *pair,
+ NiceComponent *component)
+{
+ if (component)
+ g_slist_foreach (pair->stun_transactions, priv_forget_stun_transaction, component);
+ g_slist_free_full (pair->stun_transactions, priv_free_stun_transaction);
+ pair->stun_transactions = NULL;
+}
+
+static void
+candidate_check_pair_fail (NiceStream *stream, NiceAgent *agent, CandidateCheckPair *p)
+{
NiceComponent *component;
component = nice_stream_find_component_by_id (stream, p->component_id);
-
p->state = NICE_CHECK_FAILED;
nice_debug ("Agent %p : pair %p state FAILED", agent, p);
-
- if (p->stun_message.buffer != NULL) {
- stun_message_id (&p->stun_message, id);
- stun_agent_forget_transaction (&component->stun_agent, id);
- }
-
- p->stun_message.buffer = NULL;
- p->stun_message.buffer_len = 0;
+ priv_free_all_stun_transactions (p, component);
}
/*
@@ -398,78 +684,183 @@ candidate_check_pair_fail (NiceStream *stream, NiceAgent *agent, CandidateCheckP
*
* @return will return FALSE when no more pending timers.
*/
-static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent, GTimeVal *now, gboolean *stun_transmitted)
+static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent)
{
gboolean keep_timer_going = FALSE;
- guint s_inprogress = 0, s_succeeded = 0, s_discovered = 0,
- s_nominated = 0, s_waiting_for_nomination = 0, s_valid = 0;
- guint frozen = 0, waiting = 0;
- GSList *i, *k;
+ GSList *i, *j;
+ CandidateCheckPair *pair;
+ unsigned int timeout;
+ GTimeVal now;
+ g_get_current_time (&now);
+
+ /* step: process ongoing STUN transactions */
for (i = stream->conncheck_list; i ; i = i->next) {
CandidateCheckPair *p = i->data;
+ gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN];
+ NiceComponent *component;
+ StunTransaction *stun;
- if (p->state == NICE_CHECK_IN_PROGRESS) {
- if (p->stun_message.buffer == NULL) {
- nice_debug ("Agent %p : STUN connectivity check was cancelled, marking as done.", agent);
- p->state = NICE_CHECK_FAILED;
- nice_debug ("Agent %p : pair %p state FAILED", agent, p);
- } else if (priv_timer_expired (&p->next_tick, now)) {
- switch (stun_timer_refresh (&p->timer)) {
- case STUN_USAGE_TIMER_RETURN_TIMEOUT:
- {
- /* case: error, abort processing */
- gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN];
- nice_address_to_string (&p->local->addr, tmpbuf1);
- nice_address_to_string (&p->remote->addr, tmpbuf2);
- nice_debug ("Agent %p : Retransmissions failed, giving up on connectivity check %p", agent, p);
- nice_debug ("Agent %p : Failed pair is [%s]:%u --> [%s]:%u", agent,
- tmpbuf1, nice_address_get_port (&p->local->addr),
- tmpbuf2, nice_address_get_port (&p->remote->addr));
- candidate_check_pair_fail (stream, agent, p);
- priv_print_conn_check_lists (agent, G_STRFUNC,
- ", retransmission failed");
-
- break;
- }
- case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
- {
- /* case: not ready, so schedule a new timeout */
- unsigned int timeout = stun_timer_remainder (&p->timer);
- nice_debug ("Agent %p :STUN transaction retransmitted on pair %p "
- "(timeout %dms, delay=%dms, retrans=%d).",
- agent, p, timeout, p->timer.delay, p->timer.retransmissions);
+ if (p->stun_transactions == NULL)
+ continue;
- agent_socket_send (p->sockptr, &p->remote->addr,
- stun_message_length (&p->stun_message),
- (gchar *)p->stun_buffer);
+ if (!agent_find_component (agent, p->stream_id, p->component_id,
+ NULL, &component))
+ continue;
+ /* The first stun transaction of the list may eventually be
+ * retransmitted, other stun transactions just have their
+ * timer updated.
+ */
- /* note: convert from milli to microseconds for g_time_val_add() */
- p->next_tick = *now;
- g_time_val_add (&p->next_tick, timeout * 1000);
+ j = p->stun_transactions->next;
- *stun_transmitted = TRUE;
- return TRUE;
- }
- case STUN_USAGE_TIMER_RETURN_SUCCESS:
- {
- unsigned int timeout = stun_timer_remainder (&p->timer);
+ /* process all stun transactions except the first one */
+ while (j) {
+ StunTransaction *s = j->data;
+ GSList *next = j->next;
- /* note: convert from milli to microseconds for g_time_val_add() */
- p->next_tick = *now;
- g_time_val_add (&p->next_tick, timeout * 1000);
-
- keep_timer_going = TRUE;
- break;
- }
+ if (priv_timer_expired (&s->next_tick, &now))
+ switch (stun_timer_refresh (&s->timer)) {
+ case STUN_USAGE_TIMER_RETURN_TIMEOUT:
+ priv_remove_stun_transaction (p, s, component);
+ break;
+ case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
+ timeout = stun_timer_remainder (&s->timer);
+ s->next_tick = now;
+ g_time_val_add (&s->next_tick, timeout * 1000);
+ break;
default:
- /* Nothing to do. */
break;
- }
}
+ j = next;
}
+ if (p->state != NICE_CHECK_IN_PROGRESS)
+ continue;
+
+ /* process the first stun transaction of the list */
+ stun = p->stun_transactions->data;
+ if (!priv_timer_expired (&stun->next_tick, &now))
+ continue;
+
+ switch (stun_timer_refresh (&stun->timer)) {
+ case STUN_USAGE_TIMER_RETURN_TIMEOUT:
+timer_return_timeout:
+ /* case: error, abort processing */
+ nice_address_to_string (&p->local->addr, tmpbuf1);
+ nice_address_to_string (&p->remote->addr, tmpbuf2);
+ nice_debug ("Agent %p : Retransmissions failed, giving up on "
+ "connectivity check %p", agent, p);
+ nice_debug ("Agent %p : Failed pair is [%s]:%u --> [%s]:%u", agent,
+ tmpbuf1, nice_address_get_port (&p->local->addr),
+ tmpbuf2, nice_address_get_port (&p->remote->addr));
+ candidate_check_pair_fail (stream, agent, p);
+ priv_print_conn_check_lists (agent, G_STRFUNC,
+ ", retransmission failed");
+
+ /* perform a check if a transition state from connected to
+ * ready can be performed. This may happen here, when the last
+ * in-progress pair has expired its retransmission count
+ * in priv_conn_check_tick_stream(), which is a condition to
+ * make the transition connected to ready.
+ */
+ priv_update_check_list_state_for_ready (agent, stream, component);
+ break;
+ case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
+ /* case: retransmission stopped, due to the nomination of
+ * a pair with a higher priority than this in-progress pair,
+ * ICE spec, sect 8.1.2 "Updating States", item 2.2
+ */
+ if (!p->retransmit)
+ goto timer_return_timeout;
+
+ /* case: not ready, so schedule a new timeout */
+ timeout = stun_timer_remainder (&stun->timer);
+
+ nice_debug ("Agent %p :STUN transaction retransmitted on pair %p "
+ "(timer=%d/%d %d/%dms).",
+ agent, p,
+ stun->timer.retransmissions, stun->timer.max_retransmissions,
+ stun->timer.delay - timeout, stun->timer.delay);
+
+ agent_socket_send (p->sockptr, &p->remote->addr,
+ stun_message_length (&stun->message),
+ (gchar *)stun->buffer);
+
+ /* note: convert from milli to microseconds for g_time_val_add() */
+ stun->next_tick = now;
+ g_time_val_add (&stun->next_tick, timeout * 1000);
+
+ return TRUE;
+ case STUN_USAGE_TIMER_RETURN_SUCCESS:
+ timeout = stun_timer_remainder (&stun->timer);
+
+ /* note: convert from milli to microseconds for g_time_val_add() */
+ stun->next_tick = now;
+ g_time_val_add (&stun->next_tick, timeout * 1000);
+
+ keep_timer_going = TRUE;
+ break;
+ default:
+ /* Nothing to do. */
+ break;
+ }
+ }
+
+ /* step: perform an ordinary check, ICE spec, 5.8 "Scheduling Checks"
+ * note: This code is executed when the triggered checks list is
+ * empty, and when no STUN message has been sent (pacing constraint)
+ */
+ pair = priv_conn_check_find_next_waiting (stream->conncheck_list);
+ if (pair) {
+ priv_print_conn_check_lists (agent, G_STRFUNC,
+ ", got a pair in Waiting state");
+ priv_conn_check_initiate (agent, pair);
+ return TRUE;
+ }
+
+ /* note: this is unclear in the ICE spec, but a check list in Frozen
+ * state (where all pairs are in Frozen state) is not supposed to
+ * change its state by an ordinary check, but only by the success of
+ * checks in other check lists, in priv_conn_check_unfreeze_related().
+ * The underlying idea is to concentrate the checks on a single check
+ * list initially.
+ */
+ if (priv_is_checklist_frozen (stream))
+ return keep_timer_going;
+
+ /* step: ordinary check continued, if there's no pair in the waiting
+ * state, pick a pair in the frozen state
+ */
+ pair = priv_conn_check_find_next_frozen (stream->conncheck_list);
+ if (pair) {
+ priv_print_conn_check_lists (agent, G_STRFUNC,
+ ", got a pair in Frozen state");
+ pair->state = NICE_CHECK_WAITING;
+ nice_debug ("Agent %p : pair %p state WAITING", agent, pair);
+ priv_conn_check_initiate (agent, pair);
+ return TRUE;
+ }
+ return keep_timer_going;
+}
+
+static gboolean
+priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent)
+{
+ gboolean keep_timer_going = FALSE;
+ guint s_inprogress = 0;
+ guint s_succeeded = 0;
+ guint s_discovered = 0;
+ guint s_nominated = 0;
+ guint s_waiting_for_nomination = 0;
+ guint s_valid = 0;
+ guint frozen = 0;
+ guint waiting = 0;
+ GSList *i, *k;
+
+ for (i = stream->conncheck_list; i ; i = i->next) {
+ CandidateCheckPair *p = i->data;
if (p->state == NICE_CHECK_FROZEN)
++frozen;
else if (p->state == NICE_CHECK_IN_PROGRESS)
@@ -495,12 +886,102 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen
if (s_inprogress)
keep_timer_going = TRUE;
- /* note: if some components have established connectivity,
- * but yet no nominated pair, keep timer going */
if (s_nominated < stream->n_components &&
s_waiting_for_nomination) {
- keep_timer_going = TRUE;
- if (agent->controlling_mode) {
+ if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
+ if (agent->nomination_mode == NICE_NOMINATION_MODE_REGULAR &&
+ agent->controlling_mode) {
+#define NICE_MIN_NUMBER_OF_VALID_PAIRS 2
+ /* ICE 8.1.1.1 Regular nomination
+ * we choose to nominate the valid pair of a component if
+ * - there is no pair left frozen, waiting or in-progress, or
+ * - if there are at least two valid pairs, or
+ * - if there is at least one valid pair of type HOST-HOST
+ *
+ * This is the "stopping criterion" described in 8.1.1.1, and is
+ * a "local optimization" between accumulating more valid pairs,
+ * and limiting the time spent waiting for in-progress connections
+ * checks until they finally fail.
+ */
+ GSList *component_item;
+
+ for (component_item = stream->components; component_item;
+ component_item = component_item->next) {
+ NiceComponent *component = component_item->data;
+ gboolean already_done = FALSE;
+ gboolean stopping_criterion = FALSE;
+ guint p_valid = 0;
+ guint p_frozen = 0;
+ guint p_waiting = 0;
+ guint p_inprogress = 0;
+ guint p_host_host_valid = 0;
+
+ /* verify that the choice of the pair to be nominated
+ * has not already been done
+ */
+ for (k = stream->conncheck_list; k ; k = k->next) {
+ CandidateCheckPair *p = k->data;
+ if (p->component_id == component->id) {
+ if (p->use_candidate_on_next_check)
+ already_done = TRUE;
+ if (p->state == NICE_CHECK_FROZEN)
+ p_frozen++;
+ else if (p->state == NICE_CHECK_WAITING)
+ p_waiting++;
+ else if (p->state == NICE_CHECK_IN_PROGRESS)
+ p_inprogress++;
+ if (p->valid)
+ p_valid++;
+ if (p->valid &&
+ p->local->type == NICE_CANDIDATE_TYPE_HOST &&
+ p->remote->type == NICE_CANDIDATE_TYPE_HOST)
+ p_host_host_valid++;
+ }
+ }
+
+ if (already_done)
+ continue;
+
+ stopping_criterion =
+ (p_host_host_valid > 0 ||
+ p_valid >= NICE_MIN_NUMBER_OF_VALID_PAIRS ||
+ (p_waiting == 0 && p_inprogress == 0 && p_frozen == 0));
+
+ if (!stopping_criterion)
+ continue;
+
+ /* when the stopping criterion is satisfied, we choose
+ * a pair to be nominated in the list of valid pairs,
+ * and add it to the triggered checks list
+ */
+ for (k = stream->conncheck_list; k ; k = k->next) {
+ CandidateCheckPair *p = k->data;
+ /* note: highest priority item selected (list always sorted) */
+ if (p->component_id == component->id &&
+ !p->nominated &&
+ !p->use_candidate_on_next_check &&
+ p->valid) {
+ /* According a ICE spec, sect 8.1.1.1. "Regular
+ * Nomination", we enqueue the check that produced this
+ * valid pair. When this pair has been discovered, we want
+ * to test its parent pair instead.
+ */
+ if (p->succeeded_pair != NULL) {
+ g_assert (p->state == NICE_CHECK_DISCOVERED);
+ p = p->succeeded_pair;
+ }
+ g_assert (p->state == NICE_CHECK_SUCCEEDED);
+ nice_debug ("Agent %p : restarting check %p with "
+ "USE-CANDIDATE attrib (regular nomination)", agent, p);
+ p->use_candidate_on_next_check = TRUE;
+ priv_add_pair_to_triggered_check_queue (agent, p);
+ keep_timer_going = TRUE;
+ break; /* move to the next component */
+ }
+ }
+ }
+ }
+ } else if (agent->controlling_mode) {
GSList *component_item;
for (component_item = stream->components; component_item;
@@ -515,7 +996,9 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen
p->state == NICE_CHECK_DISCOVERED)) {
nice_debug ("Agent %p : restarting check %p as the nominated pair.", agent, p);
p->nominated = TRUE;
+ priv_update_selected_pair (agent, component, p);
priv_add_pair_to_triggered_check_queue (agent, p);
+ keep_timer_going = TRUE;
break; /* move to the next component */
}
}
@@ -542,6 +1025,7 @@ conn_check_stop (NiceAgent *agent)
g_source_destroy (agent->conncheck_timer_source);
g_source_unref (agent->conncheck_timer_source);
agent->conncheck_timer_source = NULL;
+ agent->conncheck_timer_grace_period = 0;
}
@@ -557,75 +1041,83 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent)
{
CandidateCheckPair *pair = NULL;
gboolean keep_timer_going = FALSE;
- gboolean res;
- /* note: we try to only generate a single stun transaction per timer
- * callback, to respect some pacing of STUN transaction, as per
- * appendix B.1 of ICE spec.
- */
- gboolean stun_transmitted = FALSE;
GSList *i, *j;
- GTimeVal now;
- /* step: process ongoing STUN transactions */
- g_get_current_time (&now);
-
- for (j = agent->streams; j; j = j->next) {
- NiceStream *stream = j->data;
- res = priv_conn_check_tick_stream (stream, agent, &now, &stun_transmitted);
- if (res)
- keep_timer_going = res;
- if (stun_transmitted)
- return TRUE;
+ /* configure the initial state of the check lists of the agent
+ * as described in ICE spec, 5.7.4
+ *
+ * if all pairs in all check lists are in frozen state, then
+ * we are in the initial state (5.7.4, point 1.)
+ */
+ if (priv_number_of_active_check_lists (agent) == 0) {
+ /* set some pairs of the first stream in the waiting state
+ * ICE spec, 5.7.4, point 2.
+ *
+ * note: we adapt the ICE spec here, by selecting the first
+ * frozen check list, which is not necessarily the check
+ * list of the first stream (the first stream may be completed)
+ */
+ NiceStream *stream = priv_find_first_frozen_check_list (agent);
+ if (stream)
+ priv_conn_check_unfreeze_next (agent, stream);
}
- /* step: first initiate a conncheck with a pair from the triggered list */
+ /* step: perform a test from the triggered checks list,
+ * ICE spec, 5.8 "Scheduling Checks"
+ */
pair = priv_get_pair_from_triggered_check_queue (agent);
if (pair) {
priv_print_conn_check_lists (agent, G_STRFUNC,
", got a pair from triggered check list");
- priv_conn_check_initiate (agent, pair);
+ conn_check_send (agent, pair);
return TRUE;
}
- /* step: when the triggered list is empty,
- * find the highest priority waiting check and send it */
+ /* step: process ongoing STUN transactions and
+ * perform an ordinary check, ICE spec, 5.8, "Scheduling Checks"
+ */
for (i = agent->streams; i ; i = i->next) {
NiceStream *stream = i->data;
-
- pair = priv_conn_check_find_next_waiting (stream->conncheck_list);
- if (pair)
- break;
+ if (priv_conn_check_tick_stream (stream, agent))
+ keep_timer_going = TRUE;
+ if (priv_conn_check_tick_stream_nominate (stream, agent))
+ keep_timer_going = TRUE;
}
- if (pair) {
- priv_conn_check_initiate (agent, pair);
- return TRUE;
+ /* step: if no work left and a conncheck list of a stream is still
+ * frozen, set the pairs to waiting, according to ICE SPEC, sect
+ * 7.1.3.3. "Check List and Timer State Updates"
+ */
+ if (!keep_timer_going) {
+ for (i = agent->streams; i ; i = i->next) {
+ NiceStream *stream = i->data;
+ if (priv_is_checklist_frozen (stream)) {
+ nice_debug ("Agent %p : stream %d conncheck list is still "
+ "frozen, while other lists are completed. Unfreeze it.",
+ agent, stream->id);
+ keep_timer_going = priv_conn_check_unfreeze_next (agent, stream);
+ }
+ }
}
- /* step: when there's no pair in the Waiting state,
- * unfreeze a new pair and check it
+ /* note: we provide a grace period before declaring a component as
+ * failed. Components marked connected, and then ready follow another
+ * code path, and are not concerned by this grace period.
*/
- res = priv_conn_check_unfreeze_next (agent);
-
- for (i = agent->streams; i ; i = i->next) {
- NiceStream *stream = i->data;
+ if (!keep_timer_going && agent->conncheck_timer_grace_period == 0)
+ nice_debug ("Agent %p : waiting %d msecs before checking "
+ "for failed components.", agent, NICE_AGENT_MAX_TIMER_GRACE_PERIOD);
- pair = priv_conn_check_find_next_waiting (stream->conncheck_list);
- if (pair)
- break;
- }
-
- if (pair) {
- priv_print_conn_check_lists (agent, G_STRFUNC,
- ", got a pair in Waiting state");
- priv_conn_check_initiate (agent, pair);
- return TRUE;
- }
+ if (keep_timer_going)
+ agent->conncheck_timer_grace_period = 0;
+ else
+ agent->conncheck_timer_grace_period += agent->timer_ta;
/* step: stop timer if no work left */
- if (keep_timer_going != TRUE) {
- nice_debug ("Agent %p : %s: stopping conncheck timer", agent, G_STRFUNC);
+ if (!keep_timer_going &&
+ agent->conncheck_timer_grace_period >= NICE_AGENT_MAX_TIMER_GRACE_PERIOD) {
+ nice_debug ("Agent %p : checking for failed components now.", agent);
for (i = agent->streams; i; i = i->next) {
NiceStream *stream = i->data;
priv_update_check_list_failed_components (agent, stream);
@@ -635,6 +1127,7 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent)
}
}
+ nice_debug ("Agent %p : %s: stopping conncheck timer", agent, G_STRFUNC);
priv_print_conn_check_lists (agent, G_STRFUNC,
", conncheck timer stopped");
@@ -645,9 +1138,10 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent)
/* XXX: what to signal, is all processing now really done? */
nice_debug ("Agent %p : changing conncheck state to COMPLETED.", agent);
+ return FALSE;
}
- return keep_timer_going;
+ return TRUE;
}
static gboolean priv_conn_check_tick (gpointer pointer)
@@ -888,8 +1382,9 @@ static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent)
agent, buf_len, p->keepalive.stun_message.buffer);
if (buf_len > 0) {
- stun_timer_start (&p->keepalive.timer, STUN_TIMER_DEFAULT_TIMEOUT,
- STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
+ stun_timer_start (&p->keepalive.timer,
+ agent->stun_initial_timeout,
+ agent->stun_max_retransmissions);
agent->media_after_tick = FALSE;
@@ -1116,8 +1611,9 @@ static void priv_turn_allocate_refresh_tick_unlocked (CandidateRefresh *cand)
}
if (buffer_len > 0) {
- stun_timer_start (&cand->timer, STUN_TIMER_DEFAULT_TIMEOUT,
- STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
+ stun_timer_start (&cand->timer,
+ cand->agent->stun_initial_timeout,
+ cand->agent->stun_max_retransmissions);
/* send the refresh */
agent_socket_send (cand->nicesock, &cand->server,
@@ -1213,154 +1709,136 @@ static void priv_preprocess_conn_check_pending_data (NiceAgent *agent, NiceStrea
if (nice_address_equal (&icheck->from, &pair->remote->addr) &&
icheck->local_socket == pair->sockptr) {
nice_debug ("Agent %p : Updating check %p with stored early-icheck %p, %p/%u/%u (agent/stream/component).", agent, pair, icheck, agent, stream->id, component->id);
+ priv_schedule_triggered_check (agent, stream, component,
+ icheck->local_socket, pair->remote);
if (icheck->use_candidate)
priv_mark_pair_nominated (agent, stream, component, pair->local, pair->remote);
- priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, pair->remote, icheck->use_candidate);
}
}
}
-static GSList *prune_cancelled_conn_check (GSList *conncheck_list)
-{
- GSList *item = conncheck_list;
-
- while (item) {
- CandidateCheckPair *pair = item->data;
- GSList *next = item->next;
-
- if (pair->state == NICE_CHECK_CANCELLED) {
- conn_check_free_item (pair);
- conncheck_list = g_slist_delete_link (conncheck_list, item);
- }
-
- item = next;
- }
-
- return conncheck_list;
-}
-
-
/*
* Handle any processing steps for connectivity checks after
- * remote candidates have been set. This function handles
+ * remote credentials have been set. This function handles
* the special case where answerer has sent us connectivity
- * checks before the answer (containing candidate information),
+ * checks before the answer (containing credentials information),
* reaches us. The special case is documented in sect 7.2
* if ICE spec (ID-19).
*/
-void conn_check_remote_candidates_set(NiceAgent *agent, NiceStream *stream, NiceComponent *component)
+void conn_check_remote_credentials_set(NiceAgent *agent, NiceStream *stream)
{
- GSList *j, *k, *l, *m, *n;
+ GSList *j, *k, *l, *m, *n, *o;
for (j = stream->conncheck_list; j ; j = j->next) {
CandidateCheckPair *pair = j->data;
- if (pair->component_id == component->id) {
- gboolean match = FALSE;
-
- /* performn delayed processing of spec steps section 7.2.1.4,
- and section 7.2.1.5 */
- priv_preprocess_conn_check_pending_data (agent, stream, component, pair);
-
- for (k = component->incoming_checks; k; k = k->next) {
- IncomingCheck *icheck = k->data;
- /* sect 7.2.1.3., "Learning Peer Reflexive Candidates", has to
- * be handled separately */
- for (l = component->remote_candidates; l; l = l->next) {
- NiceCandidate *cand = l->data;
- if (nice_address_equal (&icheck->from, &cand->addr)) {
- match = TRUE;
- break;
- }
- }
- if (match != TRUE) {
- /* note: we have gotten an incoming connectivity check from
- * an address that is not a known remote candidate */
-
- NiceCandidate *local_candidate = NULL;
- NiceCandidate *remote_candidate = NULL;
-
- if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE ||
- agent->compatibility == NICE_COMPATIBILITY_MSN ||
- agent->compatibility == NICE_COMPATIBILITY_OC2007) {
- /* We need to find which local candidate was used */
- uint8_t uname[NICE_STREAM_MAX_UNAME];
- guint uname_len;
-
- nice_debug ("Agent %p: We have a peer-reflexive candidate in a "
- "stored pending check", agent);
-
- for (m = component->remote_candidates;
- m != NULL && remote_candidate == NULL; m = m->next) {
- for (n = component->local_candidates; n; n = n->next) {
- NiceCandidate *rcand = m->data;
- NiceCandidate *lcand = n->data;
-
- uname_len = priv_create_username (agent, stream,
- component->id, rcand, lcand,
- uname, sizeof (uname), TRUE);
-
- stun_debug ("pending check, comparing usernames of len %d and %d, equal=%d",
- icheck->username_len, uname_len,
- icheck->username && uname_len == icheck->username_len &&
- memcmp (uname, icheck->username, icheck->username_len) == 0);
- stun_debug_bytes (" first username: ",
- icheck->username,
- icheck->username? icheck->username_len : 0);
- stun_debug_bytes (" second username: ", uname, uname_len);
-
- if (icheck->username &&
- uname_len == icheck->username_len &&
- memcmp (uname, icheck->username, icheck->username_len) == 0) {
- local_candidate = lcand;
- remote_candidate = rcand;
- break;
- }
- }
- }
- } else {
- for (l = component->local_candidates; l; l = l->next) {
- NiceCandidate *cand = l->data;
- if (nice_address_equal (&cand->addr, &icheck->local_socket->addr)) {
- local_candidate = cand;
+ NiceComponent *component =
+ nice_stream_find_component_by_id (stream, pair->component_id);
+ gboolean match = FALSE;
+
+ /* performn delayed processing of spec steps section 7.2.1.4,
+ and section 7.2.1.5 */
+ priv_preprocess_conn_check_pending_data (agent, stream, component, pair);
+
+ for (k = component->incoming_checks; k; k = k->next) {
+ IncomingCheck *icheck = k->data;
+ /* sect 7.2.1.3., "Learning Peer Reflexive Candidates", has to
+ * be handled separately */
+ for (l = component->remote_candidates; l; l = l->next) {
+ NiceCandidate *cand = l->data;
+ if (nice_address_equal (&icheck->from, &cand->addr)) {
+ match = TRUE;
+ break;
+ }
+ }
+ if (match != TRUE) {
+ /* note: we have gotten an incoming connectivity check from
+ * an address that is not a known remote candidate */
+
+ NiceCandidate *local_candidate = NULL;
+ NiceCandidate *remote_candidate = NULL;
+
+ if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE ||
+ agent->compatibility == NICE_COMPATIBILITY_MSN ||
+ agent->compatibility == NICE_COMPATIBILITY_OC2007) {
+ /* We need to find which local candidate was used */
+ uint8_t uname[NICE_STREAM_MAX_UNAME];
+ guint uname_len;
+
+ nice_debug ("Agent %p: We have a peer-reflexive candidate in a "
+ "stored pending check", agent);
+
+ for (m = component->remote_candidates;
+ m != NULL && remote_candidate == NULL; m = m->next) {
+ for (n = component->local_candidates; n; n = n->next) {
+ NiceCandidate *rcand = m->data;
+ NiceCandidate *lcand = n->data;
+
+ uname_len = priv_create_username (agent, stream,
+ component->id, rcand, lcand,
+ uname, sizeof (uname), TRUE);
+
+ stun_debug ("pending check, comparing usernames of len %d and %d, equal=%d",
+ icheck->username_len, uname_len,
+ icheck->username && uname_len == icheck->username_len &&
+ memcmp (uname, icheck->username, icheck->username_len) == 0);
+ stun_debug_bytes (" first username: ",
+ icheck->username,
+ icheck->username? icheck->username_len : 0);
+ stun_debug_bytes (" second username: ", uname, uname_len);
+
+ if (icheck->username &&
+ uname_len == icheck->username_len &&
+ memcmp (uname, icheck->username, icheck->username_len) == 0) {
+ local_candidate = lcand;
+ remote_candidate = rcand;
break;
}
}
}
-
- if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE &&
- local_candidate == NULL) {
- /* if we couldn't match the username, then the matching remote
- * candidate hasn't been received yet.. we must wait */
- nice_debug ("Agent %p : Username check failed. pending check has "
- "to wait to be processed", agent);
- } else {
- NiceCandidate *candidate;
-
- nice_debug ("Agent %p : Discovered peer reflexive from early i-check",
- agent);
- candidate =
- discovery_learn_remote_peer_reflexive_candidate (agent,
- stream,
- component,
- icheck->priority,
- &icheck->from,
- icheck->local_socket,
- local_candidate, remote_candidate);
- if (candidate) {
- if (local_candidate &&
- local_candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE)
- priv_conn_check_add_for_candidate_pair_matched (agent,
- stream->id, component, local_candidate, candidate, NICE_CHECK_DISCOVERED);
- else
- conn_check_add_for_candidate (agent, stream->id, component, candidate);
-
- if (icheck->use_candidate)
- priv_mark_pair_nominated (agent, stream, component, local_candidate, candidate);
- priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, candidate, icheck->use_candidate);
+ } else {
+ for (l = component->local_candidates; l; l = l->next) {
+ NiceCandidate *cand = l->data;
+ if (nice_address_equal (&cand->addr, &icheck->local_socket->addr)) {
+ local_candidate = cand;
+ break;
}
}
}
+
+ if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE &&
+ local_candidate == NULL) {
+ /* if we couldn't match the username, then the matching remote
+ * candidate hasn't been received yet.. we must wait */
+ nice_debug ("Agent %p : Username check failed. pending check has "
+ "to wait to be processed", agent);
+ } else {
+ NiceCandidate *candidate;
+
+ nice_debug ("Agent %p : Discovered peer reflexive from early i-check",
+ agent);
+ candidate =
+ discovery_learn_remote_peer_reflexive_candidate (agent,
+ stream,
+ component,
+ icheck->priority,
+ &icheck->from,
+ icheck->local_socket,
+ local_candidate, remote_candidate);
+ if (candidate) {
+ if (local_candidate &&
+ local_candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE)
+ priv_conn_check_add_for_candidate_pair_matched (agent,
+ stream->id, component, local_candidate, candidate, NICE_CHECK_DISCOVERED);
+ else
+ conn_check_add_for_candidate (agent, stream->id, component, candidate);
+
+ priv_schedule_triggered_check (agent, stream, component,
+ icheck->local_socket, candidate);
+ if (icheck->use_candidate)
+ priv_mark_pair_nominated (agent, stream, component, local_candidate, candidate);
+ }
+ }
}
}
}
@@ -1368,12 +1846,12 @@ void conn_check_remote_candidates_set(NiceAgent *agent, NiceStream *stream, Nice
/* Once we process the pending checks, we should free them to avoid
* reprocessing them again if a dribble-mode set_remote_candidates
* is called */
- g_slist_free_full (component->incoming_checks,
- (GDestroyNotify) incoming_check_free);
- component->incoming_checks = NULL;
-
- stream->conncheck_list =
- prune_cancelled_conn_check (stream->conncheck_list);
+ for (o = stream->components; o; o = o->next) {
+ NiceComponent *component = o->data;
+ g_slist_free_full (component->incoming_checks,
+ (GDestroyNotify) incoming_check_free);
+ component->incoming_checks = NULL;
+ }
}
/*
@@ -1381,7 +1859,7 @@ void conn_check_remote_candidates_set(NiceAgent *agent, NiceStream *stream, Nice
* in ICE spec section 5.7.3 (ID-19). See also
* conn_check_add_for_candidate().
*/
-static void priv_limit_conn_check_list_size (GSList *conncheck_list, guint upper_limit)
+static GSList *priv_limit_conn_check_list_size (GSList *conncheck_list, guint upper_limit)
{
guint valid = 0;
guint cancelled = 0;
@@ -1389,22 +1867,22 @@ static void priv_limit_conn_check_list_size (GSList *conncheck_list, guint upper
while (item) {
CandidateCheckPair *pair = item->data;
+ GSList *next = item->next;
- if (pair->state != NICE_CHECK_CANCELLED) {
- valid++;
- if (valid > upper_limit) {
- pair->state = NICE_CHECK_CANCELLED;
+ valid++;
+ if (valid > upper_limit) {
+ conn_check_free_item (pair);
+ conncheck_list = g_slist_delete_link (conncheck_list, item);
cancelled++;
- }
}
-
- item = item->next;
+ item = next;
}
if (cancelled > 0)
nice_debug ("Agent : Pruned %d candidates. Conncheck list has %d elements"
" left. Maximum connchecks allowed : %d", cancelled, valid,
upper_limit);
+ return conncheck_list;
}
/*
@@ -1444,15 +1922,18 @@ static gboolean priv_update_selected_pair (NiceAgent *agent, NiceComponent *comp
* Updates the check list state.
*
* Implements parts of the algorithm described in
- * ICE sect 8.1.2. "Updating States" (ID-19): if for any
+ * ICE sect 8.1.2. "Updating States" (RFC 5245): if for any
* component, all checks have been completed and have
- * failed, mark that component's state to NICE_CHECK_FAILED.
+ * failed to produce a nominated pair, mark that component's
+ * state to NICE_CHECK_FAILED.
*
* Sends a component state changesignal via 'agent'.
*/
static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStream *stream)
{
GSList *i;
+ gboolean completed;
+ guint nominated;
/* note: emitting a signal might cause the client
* to remove the stream, thus the component count
* must be fetched before entering the loop*/
@@ -1476,6 +1957,8 @@ static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStre
if (!agent_find_component (agent, stream->id, c+1, NULL, &comp))
continue;
+ nominated = 0;
+ completed = TRUE;
for (i = stream->conncheck_list; i; i = i->next) {
CandidateCheckPair *p = i->data;
@@ -1483,16 +1966,22 @@ static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStre
g_assert (p->stream_id == stream->id);
if (p->component_id == (c + 1)) {
- if (p->state != NICE_CHECK_FAILED)
- break;
+ if (p->nominated)
+ ++nominated;
+ if (p->state != NICE_CHECK_FAILED &&
+ p->state != NICE_CHECK_SUCCEEDED &&
+ p->state != NICE_CHECK_DISCOVERED)
+ completed = FALSE;
}
}
- /* note: all checks have failed
+ /* note: all pairs are either failed or succeeded, and the component
+ * has not produced a nominated pair.
* Set the component to FAILED only if it actually had remote candidates
* that failed.. */
- if (i == NULL && comp != NULL && comp->remote_candidates != NULL)
- agent_signal_component_state_change (agent,
+ if (completed && nominated == 0 &&
+ comp != NULL && comp->remote_candidates != NULL)
+ agent_signal_component_state_change (agent,
stream->id,
(c + 1), /* component-id */
NICE_COMPONENT_STATE_FAILED);
@@ -1525,7 +2014,6 @@ static void priv_update_check_list_state_for_ready (NiceAgent *agent, NiceStream
++valid;
if (p->nominated == TRUE) {
++nominated;
- priv_update_selected_pair (agent, component, p);
}
}
}
@@ -1535,7 +2023,7 @@ static void priv_update_check_list_state_for_ready (NiceAgent *agent, NiceStream
/* Only go to READY if no checks are left in progress. If there are
* any that are kept, then this function will be called again when the
* conncheck tick timer finishes them all */
- if (priv_prune_pending_checks (stream, component->id) == 0) {
+ if (priv_prune_pending_checks (agent, stream, component->id) == 0) {
/* Continue through the states to give client code a nice
* logical progression. See http://phabricator.freedesktop.org/D218 for
* discussion. */
@@ -1564,24 +2052,59 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice
g_assert (component);
+ if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) &&
+ agent->controlling_mode)
+ return;
+
/* step: search for at least one nominated pair */
for (i = stream->conncheck_list; i; i = i->next) {
CandidateCheckPair *pair = i->data;
if (pair->local == localcand && pair->remote == remotecand) {
- nice_debug ("Agent %p : marking pair %p (%s) as nominated", agent, pair, pair->foundation);
- pair->nominated = TRUE;
+ /* ICE, 7.2.1.5. Updating the Nominated Flag */
+ /* note: TCP candidates typically produce peer reflexive
+ * candidate, generating a "discovered" pair that can be
+ * nominated.
+ */
+ if (pair->state == NICE_CHECK_SUCCEEDED &&
+ pair->discovered_pair != NULL) {
+ pair = pair->discovered_pair;
+ g_assert (pair->state == NICE_CHECK_DISCOVERED);
+ }
+
+ /* If the state of this pair is In-Progress, [...] the resulting
+ * valid pair has its nominated flag set when the response
+ * arrives.
+ */
+ if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) &&
+ pair->state == NICE_CHECK_IN_PROGRESS) {
+ pair->mark_nominated_on_response_arrival = TRUE;
+ nice_debug ("Agent %p : pair %p (%s) is in-progress, "
+ "will be nominated on response receipt.",
+ agent, pair, pair->foundation);
+ }
+
+ if (pair->valid ||
+ !NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
+ nice_debug ("Agent %p : marking pair %p (%s) as nominated",
+ agent, pair, pair->foundation);
+ pair->nominated = TRUE;
+ }
+
if (pair->valid) {
- priv_update_selected_pair (agent, component, pair);
/* Do not step down to CONNECTED if we're already at state READY*/
- if (component->state != NICE_COMPONENT_STATE_READY) {
+ if (component->state == NICE_COMPONENT_STATE_FAILED)
+ agent_signal_component_state_change (agent,
+ stream->id, component->id, NICE_COMPONENT_STATE_CONNECTING);
+ priv_update_selected_pair (agent, component, pair);
+ if (component->state == NICE_COMPONENT_STATE_CONNECTING)
/* step: notify the client of a new component state (must be done
* before the possible check list state update step */
agent_signal_component_state_change (agent,
stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED);
- }
-
}
- priv_update_check_list_state_for_ready (agent, stream, component);
+
+ if (pair->nominated)
+ priv_update_check_list_state_for_ready (agent, stream, component);
}
}
}
@@ -1624,7 +2147,7 @@ ensure_unique_priority (NiceComponent *component, guint32 priority)
*/
static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent,
guint stream_id, NiceComponent *component, NiceCandidate *local,
- NiceCandidate *remote, NiceCheckState initial_state, gboolean use_candidate)
+ NiceCandidate *remote, NiceCheckState initial_state)
{
NiceStream *stream;
CandidateCheckPair *pair;
@@ -1662,8 +2185,6 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent,
tmpbuf1, nice_address_get_port (&pair->local->addr),
tmpbuf2, nice_address_get_port (&pair->remote->addr));
}
- pair->nominated = use_candidate;
- pair->controlling = agent->controlling_mode;
pair->prflx_priority = ensure_unique_priority (component,
peer_reflexive_candidate_priority (agent, local));
@@ -1675,7 +2196,8 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent,
/* implement the hard upper limit for number of
checks (see sect 5.7.3 ICE ID-19): */
if (agent->compatibility == NICE_COMPATIBILITY_RFC5245) {
- priv_limit_conn_check_list_size (stream->conncheck_list, agent->max_conn_checks);
+ stream->conncheck_list = priv_limit_conn_check_list_size (
+ stream->conncheck_list, agent->max_conn_checks);
}
return pair;
@@ -1709,7 +2231,7 @@ static CandidateCheckPair *priv_conn_check_add_for_candidate_pair_matched (
agent, local->foundation, remote->foundation,
stream_id, component->id);
pair = priv_add_new_check_pair (agent, stream_id, component, local, remote,
- initial_state, FALSE);
+ initial_state);
if (component->state == NICE_COMPONENT_STATE_CONNECTED ||
component->state == NICE_COMPONENT_STATE_READY) {
agent_signal_component_state_change (agent,
@@ -1781,6 +2303,15 @@ int conn_check_add_for_candidate (NiceAgent *agent, guint stream_id, NiceCompone
g_assert (remote != NULL);
+ /* note: according to 7.2.1.3, "Learning Peer Reflexive Candidates",
+ * the agent does not pair this candidate with any local candidates.
+ */
+ if (agent->compatibility == NICE_COMPATIBILITY_RFC5245 &&
+ remote->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE)
+ {
+ return added;
+ }
+
for (i = component->local_candidates; i ; i = i->next) {
NiceCandidate *local = i->data;
@@ -1815,6 +2346,18 @@ int conn_check_add_for_local_candidate (NiceAgent *agent, guint stream_id, NiceC
g_assert (local != NULL);
+ /*
+ * note: according to 7.1.3.2.1 "Discovering Peer Reflexive
+ * Candidates", the peer reflexive candidate is not paired
+ * with other remote candidates
+ */
+
+ if (agent->compatibility == NICE_COMPATIBILITY_RFC5245 &&
+ local->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE)
+ {
+ return added;
+ }
+
for (i = component->remote_candidates; i ; i = i->next) {
NiceCandidate *remote = i->data;
@@ -1838,8 +2381,7 @@ static void conn_check_free_item (gpointer data)
if (pair->agent)
priv_remove_pair_from_triggered_check_queue (pair->agent, pair);
- pair->stun_message.buffer = NULL;
- pair->stun_message.buffer_len = 0;
+ priv_free_all_stun_transactions (pair, NULL);
g_slice_free (CandidateCheckPair, pair);
}
@@ -2057,30 +2599,28 @@ size_t priv_get_password (NiceAgent *agent, NiceStream *stream,
/* Implement the computation specific in RFC 5245 section 16 */
-static unsigned int priv_compute_conncheck_timer (NiceAgent *agent)
+static unsigned int priv_compute_conncheck_timer (NiceAgent *agent, NiceStream *stream)
{
- GSList *item1, *item2;
+ GSList *i;
guint waiting_and_in_progress = 0;
+ guint n = 0;
unsigned int rto = 0;
-
- for (item1 = agent->streams; item1; item1 = item1->next) {
- NiceStream *stream = item1->data;;
- for (item2 = stream->conncheck_list; item2; item2 = item2->next) {
- CandidateCheckPair *pair = item2->data;
-
- if (pair->state == NICE_CHECK_IN_PROGRESS ||
- pair->state == NICE_CHECK_WAITING)
- waiting_and_in_progress++;
- }
+ for (i = stream->conncheck_list; i ; i = i->next) {
+ CandidateCheckPair *p = i->data;
+ if (p->state == NICE_CHECK_IN_PROGRESS ||
+ p->state == NICE_CHECK_WAITING)
+ waiting_and_in_progress++;
}
- rto = agent->timer_ta * waiting_and_in_progress;
+ n = priv_number_of_active_check_lists (agent);
+ rto = agent->timer_ta * n * waiting_and_in_progress;
/* We assume non-reliable streams are RTP, so we use 100 as the max */
- nice_debug ("Agent %p : timer set to %dms (waiting+in_progress=%d)",
+ nice_debug ("Agent %p : timer set to %dms, "
+ "waiting+in_progress=%d, nb_active=%d",
agent, agent->reliable ? MAX (rto, 500) : MAX (rto, 100),
- waiting_and_in_progress);
+ waiting_and_in_progress, n);
if (agent->reliable)
return MAX (rto, 500);
else
@@ -2114,6 +2654,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair)
bool cand_use = controlling;
size_t buffer_len;
unsigned int timeout;
+ StunTransaction *stun;
if (!agent_find_component (agent, pair->stream_id, pair->component_id,
&stream, &component))
@@ -2135,99 +2676,119 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair)
nice_address_to_string (&pair->local->addr, tmpbuf1);
nice_address_to_string (&pair->remote->addr, tmpbuf2);
nice_debug ("Agent %p : STUN-CC REQ [%s]:%u --> [%s]:%u, socket=%u, "
- "pair=%s (c-id:%u), tie=%llu, username='%.*s' (%" G_GSIZE_FORMAT "), "
- "password='%.*s' (%" G_GSIZE_FORMAT "), prio=%u, cont=%d.", agent,
+ "pair=%p (c-id:%u), tie=%llu, username='%.*s' (%" G_GSIZE_FORMAT "), "
+ "password='%.*s' (%" G_GSIZE_FORMAT "), prio=%u, %s.", agent,
tmpbuf1, nice_address_get_port (&pair->local->addr),
tmpbuf2, nice_address_get_port (&pair->remote->addr),
pair->sockptr->fileno ? g_socket_get_fd(pair->sockptr->fileno) : -1,
- pair->foundation, pair->component_id,
+ pair, pair->component_id,
(unsigned long long)agent->tie_breaker,
(int) uname_len, uname, uname_len,
(int) password_len, password, password_len,
- pair->prflx_priority, controlling);
+ pair->prflx_priority,
+ controlling ? "controlling" : "controlled");
}
- if (cand_use)
+ if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
+ switch (agent->nomination_mode) {
+ case NICE_NOMINATION_MODE_REGULAR:
+ /* We are doing regular nomination, so we set the use-candidate
+ * attrib, when the controlling agent decided which valid pair to
+ * resend with this flag in priv_conn_check_tick_stream()
+ */
+ cand_use = pair->use_candidate_on_next_check;
+ nice_debug ("Agent %p : %s: set cand_use=%d "
+ "(regular nomination).", agent, G_STRFUNC, cand_use);
+ break;
+ case NICE_NOMINATION_MODE_AGGRESSIVE:
+ /* We are doing aggressive nomination, we set the use-candidate
+ * attrib in every check we send, when we are the controlling
+ * agent, RFC 5245, 8.1.1.2
+ */
+ cand_use = controlling;
+ nice_debug ("Agent %p : %s: set cand_use=%d "
+ "(aggressive nomination).", agent, G_STRFUNC, cand_use);
+ break;
+ default:
+ /* Nothing to do. */
+ break;
+ }
+ } else if (cand_use)
pair->nominated = controlling;
- if (uname_len > 0) {
- buffer_len = stun_usage_ice_conncheck_create (&component->stun_agent,
- &pair->stun_message, pair->stun_buffer, sizeof(pair->stun_buffer),
- uname, uname_len, password, password_len,
- cand_use, controlling, pair->prflx_priority,
- agent->tie_breaker,
- pair->local->foundation,
- agent_to_ice_compatibility (agent));
+ if (uname_len == 0) {
+ nice_debug ("Agent %p: no credentials found, cancelling conncheck", agent);
+ return -1;
+ }
- nice_debug ("Agent %p: conncheck created %zd - %p", agent, buffer_len,
- pair->stun_message.buffer);
+ stun = priv_add_stun_transaction (pair);
- if (agent->compatibility == NICE_COMPATIBILITY_MSN ||
- agent->compatibility == NICE_COMPATIBILITY_OC2007) {
- g_free (password);
- }
+ buffer_len = stun_usage_ice_conncheck_create (&component->stun_agent,
+ &stun->message, stun->buffer, sizeof(stun->buffer),
+ uname, uname_len, password, password_len,
+ cand_use, controlling, pair->prflx_priority,
+ agent->tie_breaker,
+ pair->local->foundation,
+ agent_to_ice_compatibility (agent));
- if (buffer_len > 0) {
- if (nice_socket_is_reliable(pair->sockptr)) {
- stun_timer_start_reliable(&pair->timer, STUN_TIMER_DEFAULT_RELIABLE_TIMEOUT);
- } else {
- stun_timer_start (&pair->timer,
- priv_compute_conncheck_timer (agent),
- STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
- }
+ nice_debug ("Agent %p: conncheck created %zd - %p", agent, buffer_len,
+ stun->message.buffer);
- /* TCP-ACTIVE candidate must create a new socket before sending
- * by connecting to the peer. The new socket is stored in the candidate
- * check pair, until we discover a new local peer reflexive */
- if (pair->sockptr->fileno == NULL &&
- pair->sockptr->type != NICE_SOCKET_TYPE_UDP_TURN &&
- pair->local->transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE) {
- NiceStream *stream2 = NULL;
- NiceComponent *component2 = NULL;
- NiceSocket *new_socket;
-
- if (agent_find_component (agent, pair->stream_id, pair->component_id,
- &stream2, &component2)) {
- new_socket = nice_tcp_active_socket_connect (pair->sockptr,
- &pair->remote->addr);
- if (new_socket) {
- pair->sockptr = new_socket;
- _priv_set_socket_tos (agent, pair->sockptr, stream2->tos);
-
- if (agent->reliable) {
- nice_socket_set_writable_callback (pair->sockptr,
- _tcp_sock_is_writable, component2);
- }
+ if (agent->compatibility == NICE_COMPATIBILITY_MSN ||
+ agent->compatibility == NICE_COMPATIBILITY_OC2007) {
+ g_free (password);
+ }
- nice_component_attach_socket (component2, new_socket);
- }
- }
- }
- /* send the conncheck */
- agent_socket_send (pair->sockptr, &pair->remote->addr,
- buffer_len, (gchar *)pair->stun_buffer);
+ if (buffer_len == 0) {
+ nice_debug ("Agent %p: buffer is empty, cancelling conncheck", agent);
+ priv_remove_stun_transaction (pair, stun, component);
+ return -1;
+ }
- if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) {
- ms_ice2_legacy_conncheck_send (&pair->stun_message, pair->sockptr,
- &pair->remote->addr);
- }
+ if (nice_socket_is_reliable(pair->sockptr)) {
+ timeout = agent->stun_reliable_timeout;
+ stun_timer_start_reliable(&stun->timer, timeout);
+ } else {
+ timeout = priv_compute_conncheck_timer (agent, stream);
+ stun_timer_start (&stun->timer, timeout, agent->stun_max_retransmissions);
+ }
- timeout = stun_timer_remainder (&pair->timer);
- /* note: convert from milli to microseconds for g_time_val_add() */
- g_get_current_time (&pair->next_tick);
- g_time_val_add (&pair->next_tick, timeout * 1000);
- } else {
- nice_debug ("Agent %p: buffer is empty, cancelling conncheck", agent);
- pair->stun_message.buffer = NULL;
- pair->stun_message.buffer_len = 0;
- return -1;
+ g_get_current_time (&stun->next_tick);
+ g_time_val_add (&stun->next_tick, timeout * 1000);
+
+ /* TCP-ACTIVE candidate must create a new socket before sending
+ * by connecting to the peer. The new socket is stored in the candidate
+ * check pair, until we discover a new local peer reflexive */
+ if (pair->sockptr->fileno == NULL &&
+ pair->sockptr->type != NICE_SOCKET_TYPE_UDP_TURN &&
+ pair->local->transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE) {
+ NiceStream *stream2 = NULL;
+ NiceComponent *component2 = NULL;
+ NiceSocket *new_socket;
+
+ if (agent_find_component (agent, pair->stream_id, pair->component_id,
+ &stream2, &component2)) {
+ new_socket = nice_tcp_active_socket_connect (pair->sockptr,
+ &pair->remote->addr);
+ if (new_socket) {
+ pair->sockptr = new_socket;
+ _priv_set_socket_tos (agent, pair->sockptr, stream2->tos);
+
+ if (agent->reliable)
+ nice_socket_set_writable_callback (pair->sockptr,
+ _tcp_sock_is_writable, component2);
+
+ nice_component_attach_socket (component2, new_socket);
+ }
}
- } else {
- nice_debug ("Agent %p: no credentials found, cancelling conncheck", agent);
- pair->stun_message.buffer = NULL;
- pair->stun_message.buffer_len = 0;
- return -1;
}
+ /* send the conncheck */
+ agent_socket_send (pair->sockptr, &pair->remote->addr,
+ buffer_len, (gchar *)stun->buffer);
+
+ if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2)
+ ms_ice2_legacy_conncheck_send (&stun->message, pair->sockptr,
+ &pair->remote->addr);
return 0;
}
@@ -2238,14 +2799,14 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair)
*
* @see priv_update_check_list_state_failed_components()
*/
-static guint priv_prune_pending_checks (NiceStream *stream, guint component_id)
+static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, guint component_id)
{
GSList *i;
guint64 highest_nominated_priority = 0;
guint in_progress = 0;
- nice_debug ("Agent XXX: Finding highest priority for component %d",
- component_id);
+ nice_debug ("Agent %p: Finding highest priority for component %d",
+ agent, component_id);
for (i = stream->conncheck_list; i; i = i->next) {
CandidateCheckPair *p = i->data;
@@ -2257,37 +2818,40 @@ static guint priv_prune_pending_checks (NiceStream *stream, guint component_id)
}
}
- nice_debug ("Agent XXX: Pruning pending checks. Highest nominated priority "
- "is %" G_GUINT64_FORMAT, highest_nominated_priority);
+ nice_debug ("Agent %p: Pruning pending checks. Highest nominated priority "
+ "is %" G_GUINT64_FORMAT, agent, highest_nominated_priority);
/* step: cancel all FROZEN and WAITING pairs for the component */
- for (i = stream->conncheck_list; i; i = i->next) {
+ i = stream->conncheck_list;
+ while (i) {
CandidateCheckPair *p = i->data;
+ GSList *next = i->next;
+
if (p->component_id == component_id) {
- if (p->state == NICE_CHECK_FROZEN ||
- p->state == NICE_CHECK_WAITING) {
- p->state = NICE_CHECK_CANCELLED;
- nice_debug ("Agent XXX : pair %p state CANCELED", p);
+ if (p->state == NICE_CHECK_FROZEN || p->state == NICE_CHECK_WAITING) {
+ nice_debug ("Agent %p : pair %p removed.", agent, p);
+ conn_check_free_item (p);
+ stream->conncheck_list = g_slist_delete_link(stream->conncheck_list, i);
}
/* note: a SHOULD level req. in ICE 8.1.2. "Updating States" (ID-19) */
- if (p->state == NICE_CHECK_IN_PROGRESS) {
- if (highest_nominated_priority != 0 &&
- p->priority < highest_nominated_priority) {
- p->stun_message.buffer = NULL;
- p->stun_message.buffer_len = 0;
- p->state = NICE_CHECK_CANCELLED;
- nice_debug ("Agent XXX : pair %p state CANCELED", p);
+ else if (p->state == NICE_CHECK_IN_PROGRESS) {
+ if (p->priority < highest_nominated_priority) {
+ p->retransmit = FALSE;
+ nice_debug ("Agent %p : pair %p will not be retransmitted.",
+ agent, p);
} else {
/* We must keep the higher priority pairs running because if a udp
* packet was lost, we might end up using a bad candidate */
- nice_debug ("Agent XXX : pair %p kept IN_PROGRESS because priority %"
+ nice_debug ("Agent %p : pair %p kept IN_PROGRESS because priority %"
G_GUINT64_FORMAT " is higher than currently nominated pair %"
- G_GUINT64_FORMAT, p, p->priority, highest_nominated_priority);
+ G_GUINT64_FORMAT, agent,
+ p, p->priority, highest_nominated_priority);
in_progress++;
}
}
}
+ i = next;
}
return in_progress;
@@ -2301,17 +2865,17 @@ static guint priv_prune_pending_checks (NiceStream *stream, guint component_id)
* @param component the check is related to
* @param local_socket socket from which the inbound check was received
* @param remote_cand remote candidate from which the inbound check was sent
- * @param use_candidate whether the original check had USE-CANDIDATE attribute set
*/
-static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *local_socket, NiceCandidate *remote_cand, gboolean use_candidate)
+static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *local_socket, NiceCandidate *remote_cand)
{
GSList *i;
NiceCandidate *local = NULL;
+ CandidateCheckPair *p;
g_assert (remote_cand != NULL);
for (i = stream->conncheck_list; i ; i = i->next) {
- CandidateCheckPair *p = i->data;
+ p = i->data;
if (p->component_id == component->id &&
p->remote == remote_cand &&
((p->local->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE &&
@@ -2322,61 +2886,67 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str
* tcp-active we don't want to retrigger a check on a pair that
* was FAILED when a peer-reflexive pair was created */
- nice_debug ("Agent %p : Found a matching pair %p for triggered check.", agent, p);
+ if (p->succeeded_pair != NULL) {
+ g_assert (p->state == NICE_CHECK_DISCOVERED);
+ p = p->succeeded_pair;
+ }
+
+ nice_debug ("Agent %p : Found a matching pair %p (%s) (%s) ...",
+ agent, p, p->foundation, priv_state_to_string (p->state));
- if (p->state == NICE_CHECK_WAITING ||
- p->state == NICE_CHECK_FROZEN)
- priv_add_pair_to_triggered_check_queue (agent, p);
- else if (p->state == NICE_CHECK_IN_PROGRESS) {
- /* XXX: according to ICE 7.2.1.4 "Triggered Checks" (ID-19),
- * we should cancel the existing one, instead we reset our timer, so
- * we'll resend the exiting transactions faster if needed...? :P
- */
- nice_debug ("Agent %p : check already in progress, "
- "restarting the timer again?: %s ..", agent,
- p->timer_restarted ? "no" : "yes");
- if (!nice_socket_is_reliable (p->sockptr) && !p->timer_restarted) {
- stun_timer_start (&p->timer,
- priv_compute_conncheck_timer (agent),
- STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
- p->timer_restarted = TRUE;
- }
- }
- else if (p->state == NICE_CHECK_SUCCEEDED ||
- p->state == NICE_CHECK_DISCOVERED) {
- nice_debug ("Agent %p : Skipping triggered check, already completed..", agent);
- /* note: this is a bit unsure corner-case -- let's do the
- same state update as for processing responses to our own checks */
- /* note: this update is required by the dribble test, to
- * ensure the transition ready -> connected -> ready, because
- * an incoming stun request generates a discovered peer reflexive,
- * that causes the ready -> connected transition.
- */
- priv_update_check_list_state_for_ready (agent, stream, component);
-
- /* note: this new check is required by the new-dribble test,
- * when early icheck on the peer controlled agent causes an
- * incoming stun request to an already succeeded (and
- * nominated) pair on the controlling agent. If the
- * controlling agent doesn't retrigger a check with
- * USE-CANDIDATE=1, the peer agent has no way to nominate it.
- *
- * This behavior differs from ICE spec 7.2.1.4
- */
- if ((agent->compatibility == NICE_COMPATIBILITY_RFC5245 ||
- agent->compatibility == NICE_COMPATIBILITY_WLM2009 ||
- agent->compatibility == NICE_COMPATIBILITY_OC2007R2) &&
- agent->controlling_mode) {
+ switch (p->state) {
+ case NICE_CHECK_WAITING:
+ case NICE_CHECK_FROZEN:
+ nice_debug ("Agent %p : pair %p added for a triggered check.",
+ agent, p);
priv_add_pair_to_triggered_check_queue (agent, p);
- conn_check_schedule_next(agent);
- }
- } else if (p->state == NICE_CHECK_FAILED) {
- /* 7.2.1.4 Triggered Checks
- * If the state of the pair is Failed, it is changed to Waiting
- and the agent MUST create a new connectivity check for that
- pair (representing a new STUN Binding request transaction), by
- enqueueing the pair in the triggered check queue. */
- priv_add_pair_to_triggered_check_queue (agent, p);
+ break;
+ case NICE_CHECK_IN_PROGRESS:
+ /* note: according to ICE SPEC sect 7.2.1.4 "Triggered Checks"
+ * we cancel the in-progress transaction, and after the
+ * retransmission timeout, we create a new connectivity check
+ * for that pair. The controlling role of this new check may
+ * be different from the role of this cancelled check.
+ *
+ * note: the flag retransmit unset means that
+ * another pair, with a higher priority is already nominated,
+ * so there's no reason to recheck this pair, since it can in
+ * no way replace the nominated one.
+ */
+ if (!nice_socket_is_reliable (p->sockptr) && p->retransmit) {
+ nice_debug ("Agent %p : pair %p added for a triggered check.",
+ agent, p);
+ priv_add_pair_to_triggered_check_queue (agent, p);
+ }
+ break;
+ case NICE_CHECK_FAILED:
+ if (p->retransmit) {
+ nice_debug ("Agent %p : pair %p added for a triggered check.",
+ agent, p);
+ priv_add_pair_to_triggered_check_queue (agent, p);
+ /* If the component for this pair is in failed state, move it
+ * back to connecting, and reinitiate the timers
+ */
+ if (component->state == NICE_COMPONENT_STATE_FAILED) {
+ agent_signal_component_state_change (agent, stream->id,
+ component->id, NICE_COMPONENT_STATE_CONNECTING);
+ conn_check_schedule_next (agent);
+ }
+ }
+ break;
+ case NICE_CHECK_SUCCEEDED:
+ nice_debug ("Agent %p : nothing to do for pair %p.", agent, p);
+ /* note: this is a bit unsure corner-case -- let's do the
+ same state update as for processing responses to our own checks */
+ /* note: this update is required by the dribble test, to
+ * ensure the transition ready -> connected -> ready, because
+ * an incoming stun request generates a discovered peer reflexive,
+ * that causes the ready -> connected transition.
+ */
+ priv_update_check_list_state_for_ready (agent, stream, component);
+ break;
+ default:
+ break;
}
/* note: the spec says the we SHOULD retransmit in-progress
@@ -2394,7 +2964,9 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str
if (i) {
nice_debug ("Agent %p : Adding a triggered check to conn.check list (local=%p).", agent, local);
- priv_add_new_check_pair (agent, stream->id, component, local, remote_cand, NICE_CHECK_WAITING, use_candidate);
+ p = priv_add_new_check_pair (agent, stream->id, component,
+ local, remote_cand, NICE_CHECK_WAITING);
+ priv_add_pair_to_triggered_check_queue (agent, p);
return TRUE;
}
else {
@@ -2447,9 +3019,7 @@ static void priv_reply_to_conn_check (NiceAgent *agent, NiceStream *stream,
}
if (rcand) {
- /* note: upon successful check, make the reserve check immediately */
- priv_schedule_triggered_check (agent, stream, component, sockptr, rcand, use_candidate);
-
+ priv_schedule_triggered_check (agent, stream, component, sockptr, rcand);
if (use_candidate)
priv_mark_pair_nominated (agent, stream, component, lcand, rcand);
}
@@ -2509,6 +3079,8 @@ static CandidateCheckPair *priv_add_peer_reflexive_pair (NiceAgent *agent, guint
pair->remote = parent_pair->remote;
pair->sockptr = local_cand->sockptr;
pair->state = NICE_CHECK_DISCOVERED;
+ parent_pair->discovered_pair = pair;
+ pair->succeeded_pair = parent_pair;
nice_debug ("Agent %p : new pair %p state DISCOVERED", agent, pair);
{
gchar tmpbuf1[INET6_ADDRSTRLEN];
@@ -2528,7 +3100,6 @@ static CandidateCheckPair *priv_add_peer_reflexive_pair (NiceAgent *agent, guint
pair->priority = nice_candidate_pair_priority (pair->remote->priority,
pair->local->priority);
pair->nominated = FALSE;
- pair->controlling = agent->controlling_mode;
pair->prflx_priority = ensure_unique_priority (component,
peer_reflexive_candidate_priority (agent, local_cand));
nice_debug ("Agent %p : added a new peer-discovered pair with foundation of '%s'.", agent, pair->foundation);
@@ -2567,14 +3138,16 @@ static void priv_check_for_role_conflict (NiceAgent *agent, gboolean control)
{
/* role conflict, change mode; wait for a new conn. check */
if (control != agent->controlling_mode) {
- nice_debug ("Agent %p : Role conflict, changing agent role to %d.", agent, control);
+ nice_debug ("Agent %p : Role conflict, changing agent role to \"%s\".",
+ agent, control ? "controlling" : "controlled");
agent->controlling_mode = control;
/* the pair priorities depend on the roles, so recalculation
* is needed */
priv_recalculate_pair_priorities (agent);
}
else
- nice_debug ("Agent %p : Role conflict, agent role already changed to %d.", agent, control);
+ nice_debug ("Agent %p : Role conflict, staying with role \"%s\".",
+ agent, control ? "controlling" : "controlled");
}
/*
@@ -2621,13 +3194,25 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent *
}
if (new_pair) {
+ /* note: when new_pair is distinct from p, it means new_pair is a
+ * previously discovered peer-reflexive candidate pair, so we don't
+ * set the valid flag on p in this case, because the valid flag is
+ * already set on the discovered pair.
+ */
+ if (new_pair == p)
+ p->valid = TRUE;
p->state = NICE_CHECK_SUCCEEDED;
+ priv_remove_pair_from_triggered_check_queue (agent, p);
+ priv_free_all_stun_transactions (p, component);
nice_debug ("Agent %p : conncheck %p SUCCEEDED.", agent, p);
- priv_conn_check_unfreeze_related (agent, stream, p);
+ nice_component_add_valid_candidate (component, remote_candidate);
}
else {
if (!local_cand) {
- if (!agent->force_relay)
+ if (!agent->force_relay) {
+ /* step: find a new local candidate, see RFC 5245 7.1.3.2.1.
+ * "Discovering Peer Reflexive Candidates"
+ */
local_cand = discovery_add_peer_reflexive_candidate (agent,
stream->id,
component->id,
@@ -2635,8 +3220,9 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent *
sockptr,
local_candidate,
remote_candidate);
- p->state = NICE_CHECK_FAILED;
- nice_debug ("Agent %p : pair %p state FAILED", agent, p);
+ nice_debug ("Agent %p : added a new peer-reflexive local candidate %p",
+ agent, local_cand);
+ }
}
/* step: add a new discovered pair (see RFC 5245 7.1.3.2.2
@@ -2644,13 +3230,23 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent *
if (local_cand)
new_pair = priv_add_peer_reflexive_pair (agent, stream->id, component,
local_cand, p);
- nice_debug ("Agent %p : conncheck %p FAILED, %p DISCOVERED.", agent, p, new_pair);
+ /* note: this is same as "adding to VALID LIST" in the spec
+ text */
+ if (new_pair)
+ new_pair->valid = TRUE;
+ /* step: The agent sets the state of the pair that *generated* the check to
+ * Succeeded, RFC 5245, 7.1.3.2.3, "Updating Pair States"
+ */
+ p->state = NICE_CHECK_SUCCEEDED;
+ priv_remove_pair_from_triggered_check_queue (agent, p);
+ priv_free_all_stun_transactions (p, component);
+ nice_debug ("Agent %p : conncheck %p SUCCEEDED, %p DISCOVERED.",
+ agent, p, new_pair);
}
- /* note: this is same as "adding to VALID LIST" in the spec
- text */
- if (new_pair)
- new_pair->valid = TRUE;
+ if (new_pair && new_pair->valid)
+ nice_component_add_valid_candidate (component, remote_candidate);
+
return new_pair;
}
@@ -2669,134 +3265,199 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre
struct sockaddr addr;
} sockaddr;
socklen_t socklen = sizeof (sockaddr);
- GSList *i;
+ GSList *i, *j;
+ guint k;
StunUsageIceReturn res;
- gboolean trans_found = FALSE;
StunTransactionId discovery_id;
StunTransactionId response_id;
stun_message_id (resp, response_id);
- for (i = stream->conncheck_list; i && trans_found != TRUE; i = i->next) {
+ for (i = stream->conncheck_list; i; i = i->next) {
CandidateCheckPair *p = i->data;
- if (p->stun_message.buffer) {
- stun_message_id (&p->stun_message, discovery_id);
-
- if (memcmp (discovery_id, response_id, sizeof(StunTransactionId)) == 0) {
- res = stun_usage_ice_conncheck_process (resp,
- &sockaddr.storage, &socklen,
- agent_to_ice_compatibility (agent));
- nice_debug ("Agent %p : stun_bind_process/conncheck for %p res %d "
- "(controlling=%d).", agent, p, (int)res, agent->controlling_mode);
-
- if (res == STUN_USAGE_ICE_RETURN_SUCCESS ||
- res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) {
- /* case: found a matching connectivity check request */
-
- CandidateCheckPair *ok_pair = NULL;
-
- nice_debug ("Agent %p : conncheck %p MATCHED.", agent, p);
- p->stun_message.buffer = NULL;
- p->stun_message.buffer_len = 0;
-
- /* step: verify that response came from the same IP address we
- * sent the original request to (see 7.1.2.1. "Failure
- * Cases") */
- if (nice_address_equal (from, &p->remote->addr) != TRUE) {
-
- p->state = NICE_CHECK_FAILED;
- if (nice_debug_is_enabled ()) {
- gchar tmpbuf[INET6_ADDRSTRLEN];
- gchar tmpbuf2[INET6_ADDRSTRLEN];
- nice_debug ("Agent %p : conncheck %p FAILED"
- " (mismatch of source address).", agent, p);
- nice_address_to_string (&p->remote->addr, tmpbuf);
- nice_address_to_string (from, tmpbuf2);
- nice_debug ("Agent %p : '%s:%u' != '%s:%u'", agent,
- tmpbuf, nice_address_get_port (&p->remote->addr),
- tmpbuf2, nice_address_get_port (from));
- }
- trans_found = TRUE;
- break;
- }
-
- /* note: CONNECTED but not yet READY, see docs */
-
- /* step: handle the possible case of a peer-reflexive
- * candidate where the mapped-address in response does
- * not match any local candidate, see 7.1.2.2.1
- * "Discovering Peer Reflexive Candidates" ICE ID-19) */
-
- if (res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) {
- /* note: this is same as "adding to VALID LIST" in the spec
- text */
- p->state = NICE_CHECK_SUCCEEDED;
- p->valid = TRUE;
- g_assert_not_reached ();
- nice_debug ("Agent %p : Mapped address not found."
- " conncheck %p SUCCEEDED.", agent, p);
- priv_conn_check_unfreeze_related (agent, stream, p);
- } else {
- ok_pair = priv_process_response_check_for_reflexive (agent,
- stream, component, p, sockptr, &sockaddr.addr,
- local_candidate, remote_candidate);
- }
-
-
- if (!ok_pair)
- ok_pair = p;
-
- /* step: updating nominated flag (ICE 7.1.2.2.4 "Updating the
- Nominated Flag" (ID-19) */
- if (ok_pair->nominated == TRUE) {
- priv_update_selected_pair (agent, component, ok_pair);
- priv_print_conn_check_lists (agent, G_STRFUNC,
- ", got a nominated pair");
+ for (j = p->stun_transactions, k = 0; j; j = j->next, k++) {
+ StunTransaction *stun = j->data;
+
+ stun_message_id (&stun->message, discovery_id);
+
+ if (memcmp (discovery_id, response_id, sizeof(StunTransactionId)))
+ continue;
+
+ res = stun_usage_ice_conncheck_process (resp,
+ &sockaddr.storage, &socklen,
+ agent_to_ice_compatibility (agent));
+ nice_debug ("Agent %p : stun_bind_process/conncheck for %p: "
+ "%s,res=%s,stun#=%d.",
+ agent, p,
+ agent->controlling_mode ? "controlling" : "controlled",
+ priv_ice_return_to_string (res), k);
+
+ if (res == STUN_USAGE_ICE_RETURN_SUCCESS ||
+ res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) {
+ /* case: found a matching connectivity check request */
+
+ CandidateCheckPair *ok_pair = NULL;
+
+ nice_debug ("Agent %p : conncheck %p MATCHED.", agent, p);
+ priv_remove_stun_transaction (p, stun, component);
+
+ /* step: verify that response came from the same IP address we
+ * sent the original request to (see 7.1.2.1. "Failure
+ * Cases") */
+ if (nice_address_equal (from, &p->remote->addr) == FALSE) {
+ candidate_check_pair_fail (stream, agent, p);
+ if (nice_debug_is_enabled ()) {
+ gchar tmpbuf[INET6_ADDRSTRLEN];
+ gchar tmpbuf2[INET6_ADDRSTRLEN];
+ nice_debug ("Agent %p : conncheck %p FAILED"
+ " (mismatch of source address).", agent, p);
+ nice_address_to_string (&p->remote->addr, tmpbuf);
+ nice_address_to_string (from, tmpbuf2);
+ nice_debug ("Agent %p : '%s:%u' != '%s:%u'", agent,
+ tmpbuf, nice_address_get_port (&p->remote->addr),
+ tmpbuf2, nice_address_get_port (from));
+ }
+ return TRUE;
+ }
- /* Do not step down to CONNECTED if we're already at state READY*/
- if (component->state != NICE_COMPONENT_STATE_READY) {
- /* step: notify the client of a new component state (must be done
- * before the possible check list state update step */
- agent_signal_component_state_change (agent,
- stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED);
- }
- }
+ /* note: CONNECTED but not yet READY, see docs */
+
+ /* step: handle the possible case of a peer-reflexive
+ * candidate where the mapped-address in response does
+ * not match any local candidate, see 7.1.2.2.1
+ * "Discovering Peer Reflexive Candidates" ICE ID-19) */
+
+ if (res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) {
+ p->state = NICE_CHECK_SUCCEEDED;
+ p->valid = TRUE;
+ nice_debug ("Agent %p : Mapped address not found."
+ " conncheck %p SUCCEEDED.", agent, p);
+ nice_component_add_valid_candidate (component, p->remote);
+ } else
+ ok_pair = priv_process_response_check_for_reflexive (agent,
+ stream, component, p, sockptr, &sockaddr.addr,
+ local_candidate, remote_candidate);
+
+ /* note: The success of this check might also
+ * cause the state of other checks to change as well, ICE
+ * spec 7.1.3.2.3
+ */
+ priv_conn_check_unfreeze_related (agent, stream, p);
+
+ /* Note: this assignment helps to reduce the numbers of cases
+ * to be tested. If ok_pair and p refer to distinct pairs, it
+ * means that ok_pair is a discovered peer reflexive one,
+ * caused by the check made on pair p. In that case, the
+ * flags to be tested are on p, but the nominated flag will be
+ * set on ok_pair. When there's no discovered pair, p and
+ * ok_pair refer to the same pair.
+ * To summarize : p is a SUCCEEDED pair, ok_pair is a
+ * DISCOVERED, VALID, and eventually NOMINATED pair.
+ */
+ if (!ok_pair)
+ ok_pair = p;
+
+ /* step: updating nominated flag (ICE 7.1.2.2.4 "Updating the
+ Nominated Flag" (ID-19) */
+ if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
+ nice_debug ("Agent %p : Updating nominated flag (%s): "
+ "ok_pair=%p (%d/%d) p=%p (%d/%d) (ucnc/mnora)",
+ agent, p->local->transport == NICE_CANDIDATE_TRANSPORT_UDP ?
+ "UDP" : "TCP",
+ ok_pair, ok_pair->use_candidate_on_next_check,
+ ok_pair->mark_nominated_on_response_arrival,
+ p, p->use_candidate_on_next_check,
+ p->mark_nominated_on_response_arrival);
+
+ if (agent->controlling_mode) {
+ switch (agent->nomination_mode) {
+ case NICE_NOMINATION_MODE_REGULAR:
+ if (p->use_candidate_on_next_check) {
+ nice_debug ("Agent %p : marking pair %p (%s) as nominated "
+ "(regular nomination, controlling, "
+ "use_cand_on_next_check=1).",
+ agent, ok_pair, ok_pair->foundation);
+ ok_pair->nominated = TRUE;
+ }
+ break;
+ case NICE_NOMINATION_MODE_AGGRESSIVE:
+ if (!p->nominated) {
+ nice_debug ("Agent %p : marking pair %p (%s) as nominated "
+ "(aggressive nomination, controlling).",
+ agent, ok_pair, ok_pair->foundation);
+ ok_pair->nominated = TRUE;
+ }
+ break;
+ default:
+ /* Nothing to do */
+ break;
+ }
+ } else {
+ if (p->mark_nominated_on_response_arrival) {
+ nice_debug ("Agent %p : marking pair %p (%s) as nominated "
+ "(%s nomination, controlled, mark_on_response=1).",
+ agent, ok_pair, ok_pair->foundation,
+ agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE ?
+ "aggressive" : "regular");
+ ok_pair->nominated = TRUE;
+ }
+ }
+ }
- /* step: update pair states (ICE 7.1.2.2.3 "Updating pair
- states" and 8.1.2 "Updating States", ID-19) */
- priv_update_check_list_state_for_ready (agent, stream, component);
+ if (ok_pair->nominated == TRUE) {
+ priv_update_selected_pair (agent, component, ok_pair);
+ priv_print_conn_check_lists (agent, G_STRFUNC,
+ ", got a nominated pair");
+
+ /* Do not step down to CONNECTED if we're already at state READY*/
+ if (component->state != NICE_COMPONENT_STATE_READY)
+ /* step: notify the client of a new component state (must be done
+ * before the possible check list state update step */
+ agent_signal_component_state_change (agent,
+ stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED);
+ }
- trans_found = TRUE;
- } else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) {
- /* case: role conflict error, need to restart with new role */
- nice_debug ("Agent %p : conncheck %p ROLE CONFLICT, restarting", agent, p);
- /* note: our role might already have changed due to an
- * incoming request, but if not, change role now;
- * follows ICE 7.1.2.1 "Failure Cases" (ID-19) */
- priv_check_for_role_conflict (agent, !p->controlling);
-
- p->stun_message.buffer = NULL;
- p->stun_message.buffer_len = 0;
- p->state = NICE_CHECK_WAITING;
- priv_add_pair_to_triggered_check_queue (agent, p);
- nice_debug ("Agent %p : pair %p state WAITING", agent, p);
- trans_found = TRUE;
- } else {
- /* case: STUN error, the check STUN context was freed */
- nice_debug ("Agent %p : conncheck %p FAILED.", agent, p);
- p->stun_message.buffer = NULL;
- p->stun_message.buffer_len = 0;
- trans_found = TRUE;
- }
+ /* step: update pair states (ICE 7.1.2.2.3 "Updating pair
+ states" and 8.1.2 "Updating States", ID-19) */
+ priv_update_check_list_state_for_ready (agent, stream, component);
+ } else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) {
+ guint64 tie;
+ gboolean controlled_mode;
+
+ /* case: role conflict error, need to restart with new role */
+ nice_debug ("Agent %p : conncheck %p ROLE CONFLICT, restarting", agent, p);
+
+ /* note: this res value indicates that the role of the peer
+ * agent has not changed after the tie-breaker comparison, so
+ * this is our role that must change. see ICE sect. 7.1.3.1
+ * "Failure Cases". Our role might already have changed due to
+ * an earlier incoming request, but if not, change role now.
+ *
+ * Sect. 7.1.3.1 is not clear on this point, but we choose to
+ * put the candidate pair in the triggered check list even
+ * when the agent did not switch its role. The reason for this
+ * interpretation is that the reception of the stun reply, even
+ * an error reply, is a good sign that this pair will be
+ * valid, if we retry the check after the role of both peers
+ * has been fixed.
+ */
+ controlled_mode = (stun_message_find64 (&stun->message,
+ STUN_ATTRIBUTE_ICE_CONTROLLED, &tie) ==
+ STUN_MESSAGE_RETURN_SUCCESS);
+
+ priv_check_for_role_conflict (agent, controlled_mode);
+ priv_remove_stun_transaction (p, stun, component);
+ priv_add_pair_to_triggered_check_queue (agent, p);
+ } else {
+ /* case: STUN error, the check STUN context was freed */
+ nice_debug ("Agent %p : conncheck %p FAILED.", agent, p);
+ candidate_check_pair_fail (stream, agent, p);
}
+ return TRUE;
}
}
-
- stream->conncheck_list =
- prune_cancelled_conn_check (stream->conncheck_list);
-
- return trans_found;
+ return FALSE;
}
/*
@@ -2846,6 +3507,7 @@ static gboolean priv_map_reply_to_discovery_request (NiceAgent *agent, StunMessa
d->server = niceaddr;
d->pending = FALSE;
+ agent->discovery_unsched_items++;
} else if (res == STUN_USAGE_BIND_RETURN_SUCCESS) {
/* case: successful binding discovery, create a new local candidate */
@@ -2930,6 +3592,41 @@ priv_add_new_turn_refresh (CandidateDiscovery *cdisco, NiceCandidate *relay_cand
return cand;
}
+static void priv_handle_turn_alternate_server (NiceAgent *agent,
+ CandidateDiscovery *disco, NiceAddress server, NiceAddress alternate)
+{
+ /* We need to cancel and reset all candidate discovery turn for the same
+ stream and type if there is an alternate server. Otherwise, we might end up
+ with two relay components on different servers, creating candidates with
+ unique foundations that only contain one component.
+ */
+ GSList *i;
+
+ for (i = agent->discovery_list; i; i = i->next) {
+ CandidateDiscovery *d = i->data;
+
+ if (!d->done &&
+ d->type == disco->type &&
+ d->stream == disco->stream &&
+ d->turn->type == disco->turn->type &&
+ nice_address_equal (&d->server, &server)) {
+ gchar ip[INET6_ADDRSTRLEN];
+ // Cancel the pending request to avoid a race condition with another
+ // component responding with another altenrate-server
+ d->stun_message.buffer = NULL;
+ d->stun_message.buffer_len = 0;
+
+ nice_address_to_string (&server, ip);
+ nice_debug ("Agent %p : Cancelling and setting alternate server %s for "
+ "CandidateDiscovery %p", agent, ip, d);
+ d->server = alternate;
+ d->turn->server = alternate;
+ d->pending = FALSE;
+ agent->discovery_unsched_items++;
+ }
+ }
+}
+
/*
* Tries to match STUN reply in 'buf' to an existing STUN discovery
* transaction. If found, a reply is sent.
@@ -2982,11 +3679,11 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage *
agent, d, (int)res);
if (res == STUN_USAGE_TURN_RETURN_ALTERNATE_SERVER) {
- /* handle alternate server */
- nice_address_set_from_sockaddr (&d->server, &alternate.addr);
- nice_address_set_from_sockaddr (&d->turn->server, &alternate.addr);
+ NiceAddress addr;
- d->pending = FALSE;
+ /* handle alternate server */
+ nice_address_set_from_sockaddr (&addr, &alternate.addr);
+ priv_handle_turn_alternate_server (agent, d, d->server, addr);
} else if (res == STUN_USAGE_TURN_RETURN_RELAY_SUCCESS ||
res == STUN_USAGE_TURN_RETURN_MAPPED_SUCCESS) {
/* case: successful allocate, create a new local candidate */
@@ -3103,6 +3800,17 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage *
recv_realm = (uint8_t *) stun_message_find (resp,
STUN_ATTRIBUTE_REALM, &recv_realm_len);
+ if ((agent->compatibility == NICE_COMPATIBILITY_OC2007 ||
+ agent->compatibility == NICE_COMPATIBILITY_OC2007R2) &&
+ alternatelen != sizeof(alternate)) {
+ NiceAddress addr;
+
+ nice_address_set_from_sockaddr (&addr, &alternate.addr);
+
+ if (!nice_address_equal (&addr, &d->server)) {
+ priv_handle_turn_alternate_server (agent, d, d->server, addr);
+ }
+ }
/* check for unauthorized error response */
if ((agent->compatibility == NICE_COMPATIBILITY_RFC5245 ||
agent->compatibility == NICE_COMPATIBILITY_OC2007 ||
@@ -3123,6 +3831,7 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage *
d->stun_resp_msg.buffer = d->stun_resp_buffer;
d->stun_resp_msg.buffer_len = sizeof(d->stun_resp_buffer);
d->pending = FALSE;
+ agent->discovery_unsched_items++;
} else {
/* case: a real unauthorized error */
d->stun_message.buffer = NULL;
@@ -3619,8 +4328,7 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream,
stun_usage_ice_conncheck_use_candidate (&req);
uint32_t priority = stun_usage_ice_conncheck_priority (&req);
- if (agent->controlling_mode ||
- agent->compatibility == NICE_COMPATIBILITY_GOOGLE ||
+ if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE ||
agent->compatibility == NICE_COMPATIBILITY_MSN ||
agent->compatibility == NICE_COMPATIBILITY_OC2007)
use_candidate = TRUE;
@@ -3628,21 +4336,21 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream,
if (stream->initial_binding_request_received != TRUE)
agent_signal_initial_binding_request_received (agent, stream);
- if (component->remote_candidates && remote_candidate == NULL) {
+ if (remote_candidate == NULL) {
nice_debug ("Agent %p : No matching remote candidate for incoming check ->"
"peer-reflexive candidate.", agent);
remote_candidate = discovery_learn_remote_peer_reflexive_candidate (
agent, stream, component, priority, from, nicesock,
local_candidate,
remote_candidate2 ? remote_candidate2 : remote_candidate);
- if(remote_candidate) {
+ if(remote_candidate && stream->remote_ufrag[0]) {
if (local_candidate &&
local_candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) {
CandidateCheckPair *pair;
pair = priv_conn_check_add_for_candidate_pair_matched (agent,
stream->id, component, local_candidate, remote_candidate,
- NICE_CHECK_DISCOVERED);
+ NICE_CHECK_SUCCEEDED);
if (pair) {
pair->valid = TRUE;
}
@@ -3651,13 +4359,15 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream,
}
}
+ nice_component_add_valid_candidate (component, remote_candidate);
+
priv_reply_to_conn_check (agent, stream, component, local_candidate,
remote_candidate, from, nicesock, rbuf_len, &msg, use_candidate);
- if (component->remote_candidates == NULL) {
+ if (stream->remote_ufrag[0] == 0) {
/* case: We've got a valid binding request to a local candidate
- * but we do not yet know remote credentials nor
- * candidates. As per sect 7.2 of ICE (ID-19), we send a reply
+ * but we do not yet know remote credentials.
+ * As per sect 7.2 of ICE (ID-19), we send a reply
* immediately but postpone all other processing until
* we get information about the remote candidates */
diff --git a/agent/conncheck.h b/agent/conncheck.h
index 431c606..e16dc67 100644
--- a/agent/conncheck.h
+++ b/agent/conncheck.h
@@ -56,7 +56,6 @@
* @NICE_CHECK_SUCCEEDED: Connection successfully checked.
* @NICE_CHECK_FAILED: No connectivity; retransmissions ceased.
* @NICE_CHECK_FROZEN: Waiting to be scheduled to %NICE_CHECK_WAITING.
- * @NICE_CHECK_CANCELLED: Check cancelled.
* @NICE_CHECK_DISCOVERED: A valid candidate pair not on the check list.
*
* States for checking a candidate pair.
@@ -68,11 +67,19 @@ typedef enum
NICE_CHECK_SUCCEEDED,
NICE_CHECK_FAILED,
NICE_CHECK_FROZEN,
- NICE_CHECK_CANCELLED,
NICE_CHECK_DISCOVERED,
} NiceCheckState;
typedef struct _CandidateCheckPair CandidateCheckPair;
+typedef struct _StunTransaction StunTransaction;
+
+struct _StunTransaction
+{
+ GTimeVal next_tick; /* next tick timestamp */
+ StunTimer timer;
+ uint8_t buffer[STUN_MAX_MESSAGE_SIZE_IPV6];
+ StunMessage message;
+};
struct _CandidateCheckPair
{
@@ -85,15 +92,15 @@ struct _CandidateCheckPair
gchar foundation[NICE_CANDIDATE_PAIR_MAX_FOUNDATION];
NiceCheckState state;
gboolean nominated;
- gboolean controlling;
- gboolean timer_restarted;
gboolean valid;
+ gboolean use_candidate_on_next_check;
+ gboolean mark_nominated_on_response_arrival;
+ gboolean retransmit; /* if the first stun request must be retransmitted */
+ CandidateCheckPair *discovered_pair;
+ CandidateCheckPair *succeeded_pair;
guint64 priority;
guint32 prflx_priority;
- GTimeVal next_tick; /* next tick timestamp */
- StunTimer timer;
- uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6];
- StunMessage stun_message;
+ GSList *stun_transactions; /* a list of ongoing stun requests */
};
int conn_check_add_for_candidate (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidate *remote);
@@ -105,7 +112,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair);
void conn_check_prune_stream (NiceAgent *agent, NiceStream *stream);
gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *udp_socket, const NiceAddress *from, gchar *buf, guint len);
gint conn_check_compare (const CandidateCheckPair *a, const CandidateCheckPair *b);
-void conn_check_remote_candidates_set(NiceAgent *agent, NiceStream *stream, NiceComponent *component);
+void conn_check_remote_credentials_set(NiceAgent *agent, NiceStream *stream);
NiceCandidateTransport conn_check_match_transport (NiceCandidateTransport transport);
void
conn_check_prune_socket (NiceAgent *agent, NiceStream *stream, NiceComponent *component,
diff --git a/agent/debug.c b/agent/debug.c
index e1a298c..84fee94 100644
--- a/agent/debug.c
+++ b/agent/debug.c
@@ -102,7 +102,7 @@ void nice_debug_init (void)
flags |= g_parse_debug_string (gflags_string, gkeys, 4);
if (gflags_string && strstr (gflags_string, "libnice-pseudotcp-verbose"))
flags |= NICE_DEBUG_PSEUDOTCP_VERBOSE;
- if (gflags_string && strstr (gflags_string, "libnice-nice-verbose")) {
+ if (gflags_string && strstr (gflags_string, "libnice-verbose")) {
flags |= NICE_DEBUG_NICE_VERBOSE;
}
diff --git a/agent/discovery.c b/agent/discovery.c
index 7a890a0..4cc99c2 100644
--- a/agent/discovery.c
+++ b/agent/discovery.c
@@ -1072,11 +1072,11 @@ static gboolean priv_discovery_tick_unlocked (gpointer pointer)
if (buffer_len > 0) {
if (nice_socket_is_reliable (cand->nicesock)) {
- stun_timer_start_reliable (&cand->timer,
- STUN_TIMER_DEFAULT_RELIABLE_TIMEOUT);
+ stun_timer_start_reliable (&cand->timer, agent->stun_reliable_timeout);
} else {
- stun_timer_start (&cand->timer, 200,
- STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
+ stun_timer_start (&cand->timer,
+ agent->stun_initial_timeout,
+ agent->stun_max_retransmissions);
}
/* send the conncheck */
diff --git a/agent/interfaces.c b/agent/interfaces.c
index 0fa2fd7..a81888e 100644
--- a/agent/interfaces.c
+++ b/agent/interfaces.c
@@ -276,6 +276,12 @@ nice_interfaces_get_local_ips (gboolean include_loopback)
nice_debug ("Ignoring loopback interface");
g_free (addr_string);
}
+#ifdef IGNORED_IFACE_PREFIX
+ } else if (g_str_has_prefix (ifa->ifa_name, IGNORED_IFACE_PREFIX)) {
+ nice_debug ("Ignoring interface %s as it matches prefix %s",
+ ifa->ifa_name, IGNORED_IFACE_PREFIX);
+ g_free (addr_string);
+#endif
} else {
if (nice_interfaces_is_private_ip (ifa->ifa_addr))
ips = add_ip_to_list (ips, addr_string, TRUE);
diff --git a/agent/stream.c b/agent/stream.c
index 8121e12..533ff15 100644
--- a/agent/stream.c
+++ b/agent/stream.c
@@ -103,27 +103,6 @@ nice_stream_find_component_by_id (NiceStream *stream, guint id)
return NULL;
}
-/*
- * Returns true if all components of the stream are either
- * 'CONNECTED' or 'READY' (connected plus nominated).
- */
-gboolean
-nice_stream_all_components_ready (NiceStream *stream)
-{
- GSList *i;
-
- for (i = stream->components; i; i = i->next) {
- NiceComponent *component = i->data;
- if (component &&
- !(component->state == NICE_COMPONENT_STATE_CONNECTED ||
- component->state == NICE_COMPONENT_STATE_READY))
- return FALSE;
- }
-
- return TRUE;
-}
-
-
/*
* Initialized the local crendentials for the stream.
*/
diff --git a/agent/stream.h b/agent/stream.h
index f9188cb..954ba66 100644
--- a/agent/stream.h
+++ b/agent/stream.h
@@ -103,9 +103,6 @@ nice_stream_new (guint n_components, NiceAgent *agent);
void
nice_stream_close (NiceStream *stream);
-gboolean
-nice_stream_all_components_ready (NiceStream *stream);
-
NiceComponent *
nice_stream_find_component_by_id (NiceStream *stream, guint id);
diff --git a/common.mk b/common.mk
index e2ca3f4..b16380d 100644
--- a/common.mk
+++ b/common.mk
@@ -4,6 +4,8 @@ pkgincludedir = $(includedir)/nice
check-valgrind:
- $(MAKE) TESTS_ENVIRONMENT="sh $$(cd "$(top_srcdir)" && pwd)/scripts/valgrind.sh" check
+ $(MAKE) TESTS_ENVIRONMENT="USE_VALGRIND=1 " check
+
+LOG_DRIVER=$(top_srcdir)/scripts/valgrind-test-driver
.PHONY: check-valgrind
diff --git a/configure.ac b/configure.ac
index b39bfe3..16988ad 100644
--- a/configure.ac
+++ b/configure.ac
@@ -57,6 +57,7 @@ AC_PROG_CC
AM_PROG_AR
LT_PREREQ([2.2.6])
LT_INIT([dlopen win32-dll disable-static])
+AC_PATH_PROG([GLIB_MKENUMS],[glib-mkenums])
# Check Operating System
AC_MSG_CHECKING([operating system])
@@ -153,7 +154,6 @@ AS_IF([test "$enable_compile_warnings" = "yes" -o \
])
AS_IF([test "$enable_compile_warnings" = "maximum" -o \
"$enable_compile_warnings" = "error"],[
- NICE_ADD_FLAG([-Wswitch-enum])
NICE_ADD_FLAG([-Wswitch-default])
NICE_ADD_FLAG([-Waggregate-return])
])
@@ -354,6 +354,20 @@ AM_CONDITIONAL([ENABLE_GTK_DOC], false)
# GObject introspection
GOBJECT_INTROSPECTION_CHECK([1.30.0])
+dnl Ignore a specific network interface name prefix from the connection check
+AC_MSG_CHECKING([whether to ignore a specific network interface name prefix])
+AC_ARG_WITH([ignored-network-interface-prefix],
+ [AS_HELP_STRING([--with-ignored-network-interface-prefix=string],
+ [Ignore network interfaces whose name starts with "string" from the ICE connection
+ check algorithm. For example, interfaces "virbr" in the case of the virtual bridge
+ handled by libvirtd, do not help in finding connectivity.])],
+ [interface_prefix="$withval"])
+AS_IF([test -n "$interface_prefix"],
+ [AC_DEFINE_UNQUOTED([IGNORED_IFACE_PREFIX],["$interface_prefix"],
+ [Ignore this network interface prefix from the connection check])
+ AC_MSG_RESULT([yes, $interface_prefix])],
+ [AC_MSG_RESULT([no])])
+
AC_CONFIG_MACRO_DIR(m4)
AC_OUTPUT
diff --git a/docs/reference/libnice/libnice-docs.xml b/docs/reference/libnice/libnice-docs.xml
index 53487bc..391be1a 100644
--- a/docs/reference/libnice/libnice-docs.xml
+++ b/docs/reference/libnice/libnice-docs.xml
@@ -105,6 +105,10 @@
<title>Index of new symbols in 0.1.14</title>
<xi:include href="xml/api-index-0.1.14.xml"><xi:fallback/></xi:include>
</index>
+ <index role="0.1.15">
+ <title>Index of new symbols in 0.1.15</title>
+ <xi:include href="xml/api-index-0.1.15.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 d377257..a481106 100644
--- a/docs/reference/libnice/libnice-sections.txt
+++ b/docs/reference/libnice/libnice-sections.txt
@@ -5,6 +5,7 @@ NiceAgent
NiceComponentState
NiceComponentType
NiceProxyType
+NiceNominationMode
NiceCompatibility
NiceAgentRecvFunc
NiceInputMessage
@@ -12,6 +13,7 @@ NiceOutputMessage
NICE_AGENT_MAX_REMOTE_CANDIDATES
nice_agent_new
nice_agent_new_reliable
+nice_agent_new_full
nice_agent_add_local_address
nice_agent_set_port_range
nice_agent_add_stream
@@ -76,6 +78,7 @@ NICE_CANDIDATE_MAX_FOUNDATION
nice_candidate_new
nice_candidate_free
nice_candidate_copy
+nice_candidate_equal_target
<SUBSECTION Standard>
NICE_TYPE_CANDIDATE
nice_candidate_get_type
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 1e7decf..9c80854 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -18,7 +18,7 @@ AM_CFLAGS = \
$(GLIB_CFLAGS) \
$(GUPNP_CFLAGS)
-bin_PROGRAMS = simple-example threaded-example sdp-example
+noinst_PROGRAMS = simple-example threaded-example sdp-example
simple_example_SOURCES = simple-example.c
simple_example_LDADD = $(top_builddir)/agent/libagent.la \
diff --git a/nice/libnice.sym b/nice/libnice.sym
index b04bb95..1e522ad 100644
--- a/nice/libnice.sym
+++ b/nice/libnice.sym
@@ -58,6 +58,7 @@ nice_agent_set_software
nice_agent_set_stream_name
nice_agent_set_stream_tos
nice_candidate_copy
+nice_candidate_equal_target
nice_candidate_free
nice_candidate_new
nice_component_state_to_string
diff --git a/scripts/valgrind-test-driver b/scripts/valgrind-test-driver
new file mode 100755
index 0000000..5b660ee
--- /dev/null
+++ b/scripts/valgrind-test-driver
@@ -0,0 +1,162 @@
+#! /bin/sh
+# test-driver - basic testsuite driver script.
+
+scriptversion=2017-04-04.22; # UTC
+
+# Copyright (C) 2011-2014 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+# Make unconditional expansion of undefined variables an error. This
+# helps a lot in preventing typo-related bugs.
+set -u
+
+usage_error ()
+{
+ echo "$0: $*" >&2
+ print_usage >&2
+ exit 2
+}
+
+print_usage ()
+{
+ cat <<END
+Usage:
+ test-driver --test-name=NAME --log-file=PATH --trs-file=PATH
+ [--expect-failure={yes|no}] [--color-tests={yes|no}]
+ [--enable-hard-errors={yes|no}] [--]
+ TEST-SCRIPT [TEST-SCRIPT-ARGUMENTS]
+The '--test-name', '--log-file' and '--trs-file' options are mandatory.
+END
+}
+
+test_name= # Used for reporting.
+log_file= # Where to save the output of the test script.
+trs_file= # Where to save the metadata of the test run.
+expect_failure=no
+color_tests=no
+enable_hard_errors=yes
+while test $# -gt 0; do
+ case $1 in
+ --help) print_usage; exit $?;;
+ --version) echo "test-driver $scriptversion"; exit $?;;
+ --test-name) test_name=$2; shift;;
+ --log-file) log_file=$2; shift;;
+ --trs-file) trs_file=$2; shift;;
+ --color-tests) color_tests=$2; shift;;
+ --expect-failure) expect_failure=$2; shift;;
+ --enable-hard-errors) enable_hard_errors=$2; shift;;
+ --) shift; break;;
+ -*) usage_error "invalid option: '$1'";;
+ *) break;;
+ esac
+ shift
+done
+
+missing_opts=
+test x"$test_name" = x && missing_opts="$missing_opts --test-name"
+test x"$log_file" = x && missing_opts="$missing_opts --log-file"
+test x"$trs_file" = x && missing_opts="$missing_opts --trs-file"
+if test x"$missing_opts" != x; then
+ usage_error "the following mandatory options are missing:$missing_opts"
+fi
+
+if test $# -eq 0; then
+ usage_error "missing argument"
+fi
+
+if test $color_tests = yes; then
+ # Keep this in sync with 'lib/am/check.am:$(am__tty_colors)'.
+ red='' # Red.
+ grn='' # Green.
+ lgn='' # Light green.
+ blu='' # Blue.
+ mgn='' # Magenta.
+ std='' # No color.
+else
+ red= grn= lgn= blu= mgn= std=
+fi
+
+do_exit='rm -f $log_file $trs_file; (exit $st); exit $st'
+trap "st=129; $do_exit" 1
+trap "st=130; $do_exit" 2
+trap "st=141; $do_exit" 13
+trap "st=143; $do_exit" 15
+
+# Test script is run here.
+top_srcdir="`dirname $0`/.."
+tests_dir="${top_srcdir}/tests"
+
+USE_VALGRIND="`printenv USE_VALGRIND`"
+
+if test "x${USE_VALGRIND}" = "x1"; then
+ ${top_srcdir}/libtool --mode=execute valgrind \
+ --leak-check=full \
+ --show-reachable=no \
+ --error-exitcode=1 \
+ --suppressions=$tests_dir/libnice.supp \
+ --num-callers=30 "$@" >$log_file 2>&1
+else
+ "$@" >$log_file 2>&1
+fi
+estatus=$?
+
+if test $enable_hard_errors = no && test $estatus -eq 99; then
+ tweaked_estatus=1
+else
+ tweaked_estatus=$estatus
+fi
+
+case $tweaked_estatus:$expect_failure in
+ 0:yes) col=$red res=XPASS recheck=yes gcopy=yes;;
+ 0:*) col=$grn res=PASS recheck=no gcopy=no;;
+ 77:*) col=$blu res=SKIP recheck=no gcopy=yes;;
+ 99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;;
+ *:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;;
+ *:*) col=$red res=FAIL recheck=yes gcopy=yes;;
+esac
+
+# Report the test outcome and exit status in the logs, so that one can
+# know whether the test passed or failed simply by looking at the '.log'
+# file, without the need of also peaking into the corresponding '.trs'
+# file (automake bug#11814).
+echo "$res $test_name (exit status: $estatus)" >>$log_file
+
+# Report outcome to console.
+echo "${col}${res}${std}: $test_name"
+
+# Register the test result, and other relevant metadata.
+echo ":test-result: $res" > $trs_file
+echo ":global-test-result: $res" >> $trs_file
+echo ":recheck: $recheck" >> $trs_file
+echo ":copy-in-global-log: $gcopy" >> $trs_file
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/scripts/valgrind.sh b/scripts/valgrind.sh
deleted file mode 100755
index 2864b6f..0000000
--- a/scripts/valgrind.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-export G_SLICE=always-malloc
-export G_DEBUG=gc-friendly
-
-tests_dir="`dirname $0`/../tests"
-
-report=`libtool --mode=execute valgrind \
- --leak-check=full \
- --show-reachable=no \
- --error-exitcode=1 \
- --suppressions=$tests_dir/libnice.supp \
- --num-callers=30 \
- $1 2>&1`
-
-#if echo "$report" | grep -q ==; then
-if test $? != 0; then
- echo "$report"
- exit 1
-fi
-
-if echo "$report" | grep -q "definitely lost"; then
- if ! echo "$report" | grep -q "definitely lost: 0 bytes"; then
- echo "$report"
- exit 1
- fi
-fi
-
diff --git a/socket/udp-turn.c b/socket/udp-turn.c
index cc3409b..190a9ea 100644
--- a/socket/udp-turn.c
+++ b/socket/udp-turn.c
@@ -174,8 +174,8 @@ priv_send_data_queue_destroy (gpointer user_data)
NiceSocket *
nice_udp_turn_socket_new (GMainContext *ctx, NiceAddress *addr,
- NiceSocket *base_socket, NiceAddress *server_addr,
- gchar *username, gchar *password,
+ NiceSocket *base_socket, const NiceAddress *server_addr,
+ const gchar *username, const gchar *password,
NiceTurnSocketCompatibility compatibility)
{
UdpTurnPriv *priv;
@@ -406,9 +406,8 @@ socket_recv_messages (NiceSocket *sock,
/* Split up the monolithic buffer again into the caller-provided buffers. */
if (parsed_buffer_length > 0 && allocated_buffer) {
- parsed_buffer_length =
- memcpy_buffer_to_input_message (message, buffer,
- parsed_buffer_length);
+ memcpy_buffer_to_input_message (message, buffer,
+ parsed_buffer_length);
}
if (allocated_buffer)
@@ -1185,7 +1184,7 @@ nice_udp_turn_socket_parse_recv_message (NiceSocket *sock, NiceSocket **from_soc
gsize
nice_udp_turn_socket_parse_recv (NiceSocket *sock, NiceSocket **from_sock,
NiceAddress *from, gsize len, guint8 *buf,
- NiceAddress *recv_from, guint8 *_recv_buf, gsize recv_len)
+ const NiceAddress *recv_from, const guint8 *_recv_buf, gsize recv_len)
{
UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv;
@@ -1195,8 +1194,8 @@ nice_udp_turn_socket_parse_recv (NiceSocket *sock, NiceSocket **from_sock,
ChannelBinding *binding = NULL;
union {
- guint8 *u8;
- guint16 *u16;
+ const guint8 *u8;
+ const guint16 *u16;
} recv_buf;
/* In the case of a reliable UDP-TURN-OVER-TCP (which means MS-TURN)
diff --git a/socket/udp-turn.h b/socket/udp-turn.h
index b1eeeb4..df10a1c 100644
--- a/socket/udp-turn.h
+++ b/socket/udp-turn.h
@@ -59,15 +59,16 @@ nice_udp_turn_socket_parse_recv_message (NiceSocket *sock, NiceSocket **from_soc
gsize
nice_udp_turn_socket_parse_recv (NiceSocket *sock, NiceSocket **from_sock,
NiceAddress *from, gsize len, guint8 *buf,
- NiceAddress *recv_from, guint8 *recv_buf, gsize recv_len);
+ const NiceAddress *recv_from, const guint8 *recv_buf, gsize recv_len);
gboolean
nice_udp_turn_socket_set_peer (NiceSocket *sock, NiceAddress *peer);
NiceSocket *
nice_udp_turn_socket_new (GMainContext *ctx, NiceAddress *addr,
- NiceSocket *base_socket, NiceAddress *server_addr,
- gchar *username, gchar *password, NiceTurnSocketCompatibility compatibility);
+ NiceSocket *base_socket, const NiceAddress *server_addr,
+ const gchar *username, const gchar *password,
+ NiceTurnSocketCompatibility compatibility);
void
nice_udp_turn_socket_set_ms_realm(NiceSocket *sock, StunMessage *msg);
diff --git a/stun/stunmessage.c b/stun/stunmessage.c
index e8184c4..4cc3392 100644
--- a/stun/stunmessage.c
+++ b/stun/stunmessage.c
@@ -120,6 +120,7 @@ stun_message_find (const StunMessage *msg, StunAttribute type,
/* Only fingerprint may come after M-I */
if (type == STUN_ATTRIBUTE_FINGERPRINT)
break;
+ return NULL;
case STUN_ATTRIBUTE_FINGERPRINT:
/* Nothing may come after FPR */
diff --git a/stun/usages/bind.c b/stun/usages/bind.c
index 8dd7afc..ee600a0 100644
--- a/stun/usages/bind.c
+++ b/stun/usages/bind.c
@@ -479,7 +479,7 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv,
size_t len;
StunUsageTransReturn ret;
int val;
- struct sockaddr_storage alternate_server;
+ struct sockaddr_storage alternate_server = { AF_UNSPEC } ;
socklen_t alternate_server_len = sizeof (alternate_server);
StunUsageBindReturn bind_ret;
@@ -491,13 +491,15 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv,
ret = stun_trans_create (&trans, SOCK_DGRAM, 0, srv, srvlen);
if (ret != STUN_USAGE_TRANS_RETURN_SUCCESS) {
stun_debug ("STUN transaction failed: couldn't create transport.");
- return STUN_USAGE_BIND_RETURN_ERROR;
+ bind_ret = STUN_USAGE_BIND_RETURN_ERROR;
+ goto done;
}
val = stun_trans_send (&trans, req_buf, len);
if (val < -1) {
stun_debug ("STUN transaction failed: couldn't send request.");
- return STUN_USAGE_BIND_RETURN_ERROR;
+ bind_ret = STUN_USAGE_BIND_RETURN_ERROR;
+ goto done;
}
stun_timer_start (&timer, STUN_TIMER_DEFAULT_TIMEOUT,
@@ -514,14 +516,16 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv,
switch (stun_timer_refresh (&timer)) {
case STUN_USAGE_TIMER_RETURN_TIMEOUT:
stun_debug ("STUN transaction failed: time out.");
- return STUN_USAGE_BIND_RETURN_TIMEOUT; // fatal error!
+ bind_ret = STUN_USAGE_BIND_RETURN_TIMEOUT; // fatal error!
+ goto done;
case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
stun_debug ("STUN transaction retransmitted (timeout %dms).",
stun_timer_remainder (&timer));
val = stun_trans_send (&trans, req_buf, len);
if (val < -1) {
stun_debug ("STUN transaction failed: couldn't resend request.");
- return STUN_USAGE_BIND_RETURN_ERROR;
+ bind_ret = STUN_USAGE_BIND_RETURN_ERROR;
+ goto done;
}
continue;
case STUN_USAGE_TIMER_RETURN_SUCCESS:
@@ -538,7 +542,10 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv,
valid = stun_agent_validate (&agent, &msg, buf, val, NULL, NULL);
if (valid == STUN_VALIDATION_UNKNOWN_ATTRIBUTE)
- return STUN_USAGE_BIND_RETURN_ERROR;
+ {
+ bind_ret = STUN_USAGE_BIND_RETURN_ERROR;
+ goto done;
+ }
if (valid != STUN_VALIDATION_SUCCESS) {
ret = STUN_USAGE_TRANS_RETURN_RETRY;
@@ -548,16 +555,22 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv,
if (bind_ret == STUN_USAGE_BIND_RETURN_ALTERNATE_SERVER) {
stun_trans_deinit (&trans);
+ assert (alternate_server.ss_family != AF_UNSPEC);
+
ret = stun_trans_create (&trans, SOCK_DGRAM, 0,
(struct sockaddr *) &alternate_server, alternate_server_len);
if (ret != STUN_USAGE_TRANS_RETURN_SUCCESS) {
- return STUN_USAGE_BIND_RETURN_ERROR;
+ bind_ret = STUN_USAGE_BIND_RETURN_ERROR;
+ goto done;
}
val = stun_trans_send (&trans, req_buf, len);
if (val < -1)
- return STUN_USAGE_BIND_RETURN_ERROR;
+ {
+ bind_ret = STUN_USAGE_BIND_RETURN_ERROR;
+ goto done;
+ }
stun_timer_start (&timer, STUN_TIMER_DEFAULT_TIMEOUT,
STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
@@ -571,5 +584,9 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv,
}
while (ret == STUN_USAGE_TRANS_RETURN_RETRY);
+done:
+ if (trans.fd != -1)
+ stun_trans_deinit (&trans);
+
return bind_ret;
}
diff --git a/stun/usages/timer.c b/stun/usages/timer.c
index 2862ab8..5370cba 100644
--- a/stun/usages/timer.c
+++ b/stun/usages/timer.c
@@ -145,7 +145,11 @@ StunUsageTimerReturn stun_timer_refresh (StunTimer *timer)
if (timer->retransmissions >= timer->max_retransmissions)
return STUN_USAGE_TIMER_RETURN_TIMEOUT;
- add_delay (&timer->deadline, timer->delay *= 2);
+ if (timer->retransmissions == timer->max_retransmissions - 1)
+ timer->delay = timer->delay / 2;
+ else
+ timer->delay = timer->delay * 2;
+ add_delay (&timer->deadline, timer->delay);
timer->retransmissions++;
return STUN_USAGE_TIMER_RETURN_RETRANSMIT;
}
diff --git a/stun/usages/timer.h b/stun/usages/timer.h
index e74353b..097e75b 100644
--- a/stun/usages/timer.h
+++ b/stun/usages/timer.h
@@ -132,7 +132,11 @@ struct stun_timer_s {
* The default intial timeout to use for the timer
* RFC recommendds 500, but it's ridiculous, 50ms is known to work in most
* cases as it is also what is used by SIP style VoIP when sending A-Law and
- * mu-Law audio, so 200ms should be hyper safe.
+ * mu-Law audio, so 200ms should be hyper safe. With an initial timeout
+ * of 200ms, a default of 7 transmissions, the last timeout will be
+ * 16 * 200ms, and we expect to receive a response from the stun server
+ * before (1 + 2 + 4 + 8 + 16 + 32 + 16) * 200ms = 15200 ms after the initial
+ * stun request has been sent.
*/
#define STUN_TIMER_DEFAULT_TIMEOUT 200
diff --git a/stun/usages/turn.c b/stun/usages/turn.c
index 3b94959..ec12642 100644
--- a/stun/usages/turn.c
+++ b/stun/usages/turn.c
@@ -300,6 +300,17 @@ StunUsageTurnReturn stun_usage_turn_process (StunMessage *msg,
stun_debug (" STUN error message received (code: %d)", code);
/* ALTERNATE-SERVER mechanism */
+ if (compatibility == STUN_USAGE_TURN_COMPATIBILITY_OC2007 &&
+ alternate_server && alternate_server_len &&
+ stun_message_find_addr (msg, STUN_ATTRIBUTE_MS_ALTERNATE_SERVER,
+ alternate_server,
+ alternate_server_len) == STUN_MESSAGE_RETURN_SUCCESS) {
+ stun_debug ("Found alternate server");
+ /* The ALTERNATE_SERVER will always be returned by the MS turn server.
+ * We need to check if the ALTERNATE_SERVER is the same as the current
+ * server to decide whether we need to switch servers or not.
+ */
+ }
if ((code / 100) == 3) {
if (alternate_server && alternate_server_len) {
if (stun_message_find_addr (msg, STUN_ATTRIBUTE_ALTERNATE_SERVER,
diff --git a/stun/usages/turn.h b/stun/usages/turn.h
index 7a2d4e6..83fa00a 100644
--- a/stun/usages/turn.h
+++ b/stun/usages/turn.h
@@ -256,6 +256,10 @@ size_t stun_usage_turn_create_permission (StunAgent *agent, StunMessage *msg,
* Allocate request, in case the currently used TURN server is requesting the use
* of an alternate server. This argument will only be filled if the return value
* of the function is #STUN_USAGE_TURN_RETURN_ALTERNATE_SERVER
+ * In the case of @STUN_USAGE_TURN_COMPATIBILITY_OC2007 compatibility, the
+ * @alternate_server could be filled at any time, and should only be considered
+ * if the request was sent to a different server than the address returned
+ * in the @alternate_server field
* @alternate_server_len: The length of @alternate_server
* @bandwidth: A pointer to fill with the bandwidth the TURN server allocated us
* @lifetime: A pointer to fill with the lifetime of the allocation
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7bfe075..30d6f8e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -24,7 +24,7 @@ AM_CPPFLAGS = -DG_LOG_DOMAIN=\"libnice-tests\"
AM_TESTS_ENVIRONMENT = \
G_MESSAGES_DEBUG=all \
- NICE_DEBUG=all;
+ NICE_DEBUG=all
COMMON_LDADD = $(top_builddir)/agent/libagent.la $(top_builddir)/socket/libsocket.la $(GLIB_LIBS) $(GUPNP_LIBS)
@@ -45,8 +45,8 @@ check_PROGRAMS = \
test-send-recv \
test-socket-is-based-on \
test-priority \
- test-mainloop \
test-fullmode \
+ test-different-number-streams \
test-restart \
test-fallback \
test-thread \
@@ -55,7 +55,9 @@ check_PROGRAMS = \
test-tcp \
test-icetcp \
test-credentials \
- test-turn
+ test-turn \
+ test-drop-invalid \
+ test-nomination
dist_check_SCRIPTS = \
check-test-fullmode-with-stun.sh \
@@ -113,6 +115,8 @@ test_mainloop_LDADD = $(COMMON_LDADD)
test_fullmode_LDADD = $(COMMON_LDADD)
+test_different_number_streams_LDADD = $(COMMON_LDADD)
+
test_restart_LDADD = $(COMMON_LDADD)
test_fallback_LDADD = $(COMMON_LDADD)
@@ -129,6 +133,10 @@ test_credentials_LDADD = $(COMMON_LDADD)
test_turn_LDADD = $(COMMON_LDADD)
+test_drop_invalid_LDADD = $(COMMON_LDADD)
+
+test_nomination_LDADD = $(COMMON_LDADD)
+
test_gstreamer_CFLAGS = $(AM_CFLAGS) $(GST_CHECK_CFLAGS)
test_gstreamer_LDADD = -lnice -L$(top_builddir)/nice/.libs $(GLIB_LIBS) $(GUPNP_LIBS) $(GST_CHECK_LIBS) $(GST_LIBS)
diff --git a/tests/test-credentials.c b/tests/test-credentials.c
index 1de4e49..f1ea89d 100644
--- a/tests/test-credentials.c
+++ b/tests/test-credentials.c
@@ -184,6 +184,8 @@ int main (void)
nice_agent_get_local_credentials (lagent, 1, &ufrag, &password);
g_assert (g_strcmp0("unicorns", ufrag) == 0);
g_assert (g_strcmp0("awesome", password) == 0);
+ g_free (ufrag);
+ g_free (password);
nice_agent_gather_candidates (lagent, 1);
nice_agent_gather_candidates (ragent, 1);
diff --git a/tests/test-different-number-streams.c b/tests/test-different-number-streams.c
new file mode 100644
index 0000000..7fd4763
--- /dev/null
+++ b/tests/test-different-number-streams.c
@@ -0,0 +1,208 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "agent.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#define ADD_2_STREAMS TRUE
+#define USE_SECOND_STREAM TRUE
+
+static GMainLoop *global_mainloop = NULL;
+
+static guint global_components_ready = 0;
+static guint global_components_ready_exit = 0;
+
+static gboolean timer_cb (gpointer pointer)
+{
+ g_debug ("test-different-number-streams:%s: %p", G_STRFUNC, pointer);
+
+ /* signal status via a global variable */
+
+ /* note: should not be reached, abort */
+ g_error ("ERROR: test has got stuck, aborting...");
+
+ return FALSE;
+}
+
+static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer data)
+{
+ g_debug ("%p: gathering done (stream_id: %u)", agent, stream_id);
+}
+
+static void cb_component_state_changed (NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer data)
+{
+ g_debug ("%p: component state changed (stream_id: %u, component_id: %u, state: %s)",
+ agent, stream_id, component_id, nice_component_state_to_string (state));
+
+ if (state == NICE_COMPONENT_STATE_READY) {
+ global_components_ready++;
+ }
+
+ /* signal status via a global variable */
+ if (global_components_ready == global_components_ready_exit) {
+ g_debug ("Components ready/failed achieved. Stopping mailoop");
+ g_main_loop_quit (global_mainloop);
+ }
+}
+
+static void set_candidates (NiceAgent *from, guint from_stream,
+ NiceAgent *to, guint to_stream, guint component)
+{
+ GSList *cands = NULL, *i;
+
+ cands = nice_agent_get_local_candidates (from, from_stream, component);
+ nice_agent_set_remote_candidates (to, to_stream, component, cands);
+
+ for (i = cands; i; i = i->next)
+ nice_candidate_free ((NiceCandidate *) i->data);
+ g_slist_free (cands);
+}
+
+static void cb_nice_recv (NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer user_data)
+{
+ g_debug ("%p: recv (stream_id: %u, component_id: %u)", agent, stream_id, component_id);
+}
+
+int main (void)
+{
+ NiceAgent *lagent, *ragent;
+ guint timer_id;
+ guint ls_id, rs_id_1, rs_id_2;
+ gchar *lufrag = NULL, *lpassword = NULL;
+ gchar *rufrag1 = NULL, *rpassword1 = NULL, *rufrag2 = NULL, *rpassword2 = NULL;
+
+#ifdef G_OS_WIN32
+ WSADATA w;
+
+ WSAStartup(0x0202, &w);
+#endif
+
+ global_mainloop = g_main_loop_new (NULL, FALSE);
+
+ /* step: create the agents L and R */
+ lagent = nice_agent_new (g_main_loop_get_context (global_mainloop),
+ NICE_COMPATIBILITY_GOOGLE);
+ g_debug ("lagent: %p", lagent);
+ nice_agent_set_software (lagent, "test-different-number-streams, Left Agent");
+ g_object_set (G_OBJECT (lagent), "ice-tcp", FALSE, NULL);
+ g_object_set (G_OBJECT (lagent), "controlling-mode", TRUE, NULL);
+ g_object_set (G_OBJECT (lagent), "upnp", FALSE, NULL);
+ g_signal_connect (G_OBJECT (lagent), "candidate-gathering-done",
+ G_CALLBACK (cb_candidate_gathering_done), NULL);
+ g_signal_connect (G_OBJECT (lagent), "component-state-changed",
+ G_CALLBACK (cb_component_state_changed), NULL);
+
+ ragent = nice_agent_new (g_main_loop_get_context (global_mainloop),
+ NICE_COMPATIBILITY_GOOGLE);
+ g_debug ("ragent: %p", ragent);
+ nice_agent_set_software (ragent, "test-different-number-streams, Right Agent");
+ g_object_set (G_OBJECT (ragent), "ice-tcp", FALSE, NULL);
+ g_object_set (G_OBJECT (ragent), "controlling-mode", FALSE, NULL);
+ g_object_set (G_OBJECT (ragent), "upnp", FALSE, NULL);
+ g_signal_connect (G_OBJECT (ragent), "candidate-gathering-done",
+ G_CALLBACK (cb_candidate_gathering_done), NULL);
+ g_signal_connect (G_OBJECT (ragent), "component-state-changed",
+ G_CALLBACK (cb_component_state_changed), NULL);
+
+ /* step: add a timer to catch state changes triggered by signals */
+ timer_id = g_timeout_add (30000, timer_cb, NULL);
+
+ ls_id = nice_agent_add_stream (lagent, 2);
+ g_assert (ls_id > 0);
+ nice_agent_get_local_credentials(lagent, ls_id, &lufrag, &lpassword);
+
+ /* 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, NULL);
+ nice_agent_attach_recv (lagent, ls_id, NICE_COMPONENT_TYPE_RTCP,
+ g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL);
+
+ global_components_ready_exit = 4;
+
+ if (ADD_2_STREAMS) {
+ rs_id_1 = nice_agent_add_stream (ragent, 2);
+ g_assert (rs_id_1 > 0);
+ nice_agent_get_local_credentials(ragent, rs_id_1, &rufrag1, &rpassword1);
+
+ rs_id_2 = nice_agent_add_stream (ragent, 2);
+ g_assert (rs_id_2 > 0);
+ nice_agent_get_local_credentials(ragent, rs_id_2, &rufrag2, &rpassword2);
+
+ nice_agent_set_remote_credentials (ragent, rs_id_2, lufrag, lpassword);
+ nice_agent_set_remote_credentials (lagent, ls_id, rufrag2, rpassword2);
+
+ g_assert (nice_agent_gather_candidates (lagent, ls_id) == TRUE);
+ g_assert (nice_agent_gather_candidates (ragent, rs_id_2) == TRUE);
+ g_assert (nice_agent_gather_candidates (ragent, rs_id_1) == TRUE);
+
+ if (USE_SECOND_STREAM) {
+ set_candidates (ragent, rs_id_2, lagent, ls_id, NICE_COMPONENT_TYPE_RTP);
+ set_candidates (ragent, rs_id_2, lagent, ls_id, NICE_COMPONENT_TYPE_RTCP);
+ set_candidates (lagent, ls_id, ragent, rs_id_2, NICE_COMPONENT_TYPE_RTP);
+ set_candidates (lagent, ls_id, ragent, rs_id_2, NICE_COMPONENT_TYPE_RTCP);
+ } else {
+ set_candidates (ragent, rs_id_1, lagent, ls_id, NICE_COMPONENT_TYPE_RTP);
+ set_candidates (ragent, rs_id_1, lagent, ls_id, NICE_COMPONENT_TYPE_RTCP);
+ set_candidates (lagent, ls_id, ragent, rs_id_1, NICE_COMPONENT_TYPE_RTP);
+ set_candidates (lagent, ls_id, ragent, rs_id_1, NICE_COMPONENT_TYPE_RTCP);
+ }
+
+ /* step: attach to mainloop (needed to register the fds) */
+ nice_agent_attach_recv (ragent, rs_id_1, NICE_COMPONENT_TYPE_RTP,
+ g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL);
+ nice_agent_attach_recv (ragent, rs_id_1, NICE_COMPONENT_TYPE_RTCP,
+ g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL);
+ nice_agent_attach_recv (ragent, rs_id_2, NICE_COMPONENT_TYPE_RTP,
+ g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL);
+ nice_agent_attach_recv (ragent, rs_id_2, NICE_COMPONENT_TYPE_RTCP,
+ g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL);
+ } else {
+ rs_id_1 = nice_agent_add_stream (ragent, 2);
+ g_assert (rs_id_1 > 0);
+ nice_agent_get_local_credentials(ragent, rs_id_1, &rufrag1, &rpassword1);
+
+ nice_agent_set_remote_credentials (ragent, rs_id_1, lufrag, lpassword);
+ nice_agent_set_remote_credentials (lagent, ls_id, rufrag1, rpassword1);
+
+ g_assert (nice_agent_gather_candidates (lagent, ls_id) == TRUE);
+ g_assert (nice_agent_gather_candidates (ragent, rs_id_1) == TRUE);
+
+ /* step: attach to mainloop (needed to register the fds) */
+ nice_agent_attach_recv (ragent, rs_id_1, NICE_COMPONENT_TYPE_RTP,
+ g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL);
+ nice_agent_attach_recv (ragent, rs_id_1, NICE_COMPONENT_TYPE_RTCP,
+ g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL);
+
+ set_candidates (ragent, rs_id_1, lagent, ls_id, NICE_COMPONENT_TYPE_RTP);
+ set_candidates (ragent, rs_id_1, lagent, ls_id, NICE_COMPONENT_TYPE_RTCP);
+ set_candidates (lagent, ls_id, ragent, rs_id_1, NICE_COMPONENT_TYPE_RTP);
+ set_candidates (lagent, ls_id, ragent, rs_id_1, NICE_COMPONENT_TYPE_RTCP);
+ }
+
+ /* step: run the mainloop until connectivity checks succeed
+ * (see timer_cb() above) */
+ g_main_loop_run (global_mainloop);
+
+ g_free (lufrag);
+ g_free (lpassword);
+ g_free (rufrag1);
+ g_free (rpassword1);
+ g_free (rufrag2);
+ g_free (rpassword2);
+ 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 0;
+}
diff --git a/tests/test-drop-invalid.c b/tests/test-drop-invalid.c
new file mode 100644
index 0000000..97e3586
--- /dev/null
+++ b/tests/test-drop-invalid.c
@@ -0,0 +1,517 @@
+/*
+ * This file is part of the Nice GLib ICE library.
+ *
+ * Unit test for ICE full-mode related features.
+ *
+ * (C) 2007 Nokia Corporation. All rights reserved.
+ * Contact: Kai Vehmanen
+ * (C) 2017 Collabora Ltd
+ * Contact: Olivier Crete <olivier.crete@collabora.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
+ *
+ * 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 "socket/socket.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+
+static NiceComponentState global_lagent_state[2] = { NICE_COMPONENT_STATE_LAST, NICE_COMPONENT_STATE_LAST };
+static NiceComponentState global_ragent_state[2] = { NICE_COMPONENT_STATE_LAST, 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 guint global_exit_when_ibr_received = 0;
+
+static void priv_print_global_status (void)
+{
+ g_debug ("\tgathering_done=%d", global_lagent_gathering_done && global_ragent_gathering_done);
+ g_debug ("\tlstate[rtp]=%d [rtcp]=%d", global_lagent_state[0], global_lagent_state[1]);
+ g_debug ("\trstate[rtp]=%d [rtcp]=%d", global_ragent_state[0], global_ragent_state[1]);
+ g_debug ("\tL cands=%d R cands=%d", global_lagent_cands, global_ragent_cands);
+}
+
+static gboolean timer_cb (gpointer pointer)
+{
+ g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, pointer);
+
+ /* signal status via a global variable */
+
+ /* note: should not be reached, abort */
+ g_error ("ERROR: test has got stuck, aborting...");
+
+ return FALSE;
+}
+
+static void cb_nice_recv (NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer user_data)
+{
+ g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, user_data);
+
+ /* XXX: dear compiler, these are for you: */
+ (void)agent; (void)stream_id; (void)component_id; (void)buf;
+
+ /* Core of the test
+ * Assert on any unreleated packet received. This would include anything
+ * send before the negotiation is over.
+ */
+ g_assert (len == 16);
+ g_assert (strncmp ("1234567812345678", buf, 16) == 0);
+
+ if (component_id == 2)
+ return;
+
+ if (GPOINTER_TO_UINT (user_data) == 2) {
+ g_debug ("right agent received %d bytes, stopping mainloop", len);
+ global_ragent_read = len;
+ g_main_loop_quit (global_mainloop);
+ }
+}
+
+static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer data)
+{
+ g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, data);
+
+ if (GPOINTER_TO_UINT (data) == 1)
+ global_lagent_gathering_done = TRUE;
+ else if (GPOINTER_TO_UINT (data) == 2)
+ global_ragent_gathering_done = TRUE;
+
+ if (global_lagent_gathering_done &&
+ global_ragent_gathering_done)
+ 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)
+{
+ gboolean ready_to_connected = FALSE;
+ g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, data);
+
+ if (GPOINTER_TO_UINT (data) == 1) {
+ if (global_lagent_state[component_id - 1] == NICE_COMPONENT_STATE_READY &&
+ state == NICE_COMPONENT_STATE_CONNECTED)
+ ready_to_connected = TRUE;
+ global_lagent_state[component_id - 1] = state;
+ } else if (GPOINTER_TO_UINT (data) == 2) {
+ if (global_ragent_state[component_id - 1] == NICE_COMPONENT_STATE_READY &&
+ state == NICE_COMPONENT_STATE_CONNECTED)
+ ready_to_connected = TRUE;
+ global_ragent_state[component_id - 1] = state;
+ }
+
+ if (state == NICE_COMPONENT_STATE_READY)
+ global_components_ready++;
+ else if (state == NICE_COMPONENT_STATE_CONNECTED && ready_to_connected)
+ global_components_ready--;
+ if (state == NICE_COMPONENT_STATE_FAILED)
+ global_components_failed++;
+
+ g_debug ("test-drop-invalid: checks READY/EXIT-AT %u/%u.", global_components_ready, global_components_ready_exit);
+ g_debug ("test-drop-invalid: checks FAILED/EXIT-AT %u/%u.", global_components_failed, global_components_failed_exit);
+
+ /* signal status via a global variable */
+ if (global_components_ready == global_components_ready_exit &&
+ global_components_failed == global_components_failed_exit) {
+ g_debug ("Components ready/failed achieved. Stopping mailoop");
+ 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-drop-invalid:%s: %p", G_STRFUNC, data);
+
+ if (GPOINTER_TO_UINT (data) == 1)
+ ++global_lagent_cands;
+ else if (GPOINTER_TO_UINT (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-drop-invalid:%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-drop-invalid:%s: %p", G_STRFUNC, data);
+
+ if (GPOINTER_TO_UINT (data) == 1)
+ global_lagent_ibr_received = TRUE;
+ else if (GPOINTER_TO_UINT (data) == 2)
+ global_ragent_ibr_received = TRUE;
+
+ if (global_exit_when_ibr_received) {
+ g_debug ("Received initial binding request. Stopping mailoop");
+ g_main_loop_quit (global_mainloop);
+ }
+
+ /* XXX: dear compiler, these are for you: */
+ (void)agent; (void)stream_id; (void)data;
+}
+
+static void set_candidates (NiceAgent *from, guint from_stream,
+ NiceAgent *to, guint to_stream, guint component)
+{
+ GSList *cands = NULL;
+ GSList *peer_cands = NULL;
+ GSList *item1, *item2;
+
+ cands = nice_agent_get_local_candidates (from, from_stream, component);
+ peer_cands = nice_agent_get_local_candidates (to, to_stream, component);
+
+ /*
+ * Core of the test:
+ *
+ * Send packets that shoudl be dropped.
+ */
+
+ for (item1 = cands; item1; item1 = item1->next) {
+ NiceCandidate *cand = item1->data;
+ NiceSocket *nicesock = cand->sockptr;
+
+ g_assert (nicesock);
+
+ for (item2 = peer_cands; item2; item2 = item2->next) {
+ NiceCandidate *target_cand = item2->data;
+
+ nice_socket_send (nicesock, &target_cand->addr, 12, "123456789AB");
+ }
+
+ }
+
+ nice_agent_set_remote_candidates (to, to_stream, component, cands);
+
+ g_slist_free_full (cands, (GDestroyNotify) nice_candidate_free);
+ g_slist_free_full (peer_cands, (GDestroyNotify) nice_candidate_free);
+}
+
+static void set_credentials (NiceAgent *lagent, guint lstream,
+ NiceAgent *ragent, guint rstream)
+{
+ gchar *ufrag = NULL, *password = NULL;
+
+ nice_agent_get_local_credentials(lagent, lstream, &ufrag, &password);
+ nice_agent_set_remote_credentials (ragent, rstream, ufrag, password);
+ g_free (ufrag);
+ g_free (password);
+ nice_agent_get_local_credentials(ragent, rstream, &ufrag, &password);
+ nice_agent_set_remote_credentials (lagent, lstream, ufrag, password);
+ g_free (ufrag);
+ g_free (password);
+}
+
+static int run_full_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *baseaddr, guint ready, guint failed)
+{
+ guint ls_id, rs_id;
+ gint ret;
+
+ /* XXX: dear compiler, this is for you */
+ (void)baseaddr;
+
+ /* step: initialize variables modified by the callbacks */
+ global_components_ready = 0;
+ global_components_ready_exit = ready;
+ global_components_failed = 0;
+ global_components_failed_exit = failed;
+ 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;
+
+ g_object_set (G_OBJECT (lagent), "controlling-mode", TRUE, NULL);
+ g_object_set (G_OBJECT (ragent), "controlling-mode", FALSE, 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 (ls_id > 0);
+ g_assert (rs_id > 0);
+
+ /* Gather candidates and test nice_agent_set_port_range */
+ nice_agent_set_port_range (lagent, ls_id, 1, 10000, 10000);
+ nice_agent_set_port_range (lagent, ls_id, 2, 10001, 10001);
+ nice_agent_set_port_range (ragent, rs_id, 1, 12345, 12345);
+ nice_agent_set_port_range (ragent, rs_id, 2, 10000, 10001);
+ g_assert (nice_agent_gather_candidates (lagent, ls_id) == TRUE);
+ g_assert (nice_agent_gather_candidates (ragent, rs_id) == FALSE);
+ g_assert (nice_agent_get_local_candidates (ragent, rs_id, 1) == NULL);
+ g_assert (nice_agent_get_local_candidates (ragent, rs_id, 2) == NULL);
+ nice_agent_set_port_range (ragent, rs_id, 2, 10000, 10002);
+ g_assert (nice_agent_gather_candidates (ragent, rs_id) == TRUE);
+
+ {
+ GSList *cands = NULL, *i;
+ NiceCandidate *cand = NULL;
+
+ cands = nice_agent_get_local_candidates (lagent, ls_id, 1);
+ g_assert (g_slist_length (cands) == 1);
+ cand = cands->data;
+ g_assert (cand->type == NICE_CANDIDATE_TYPE_HOST);
+ g_assert (nice_address_get_port (&cand->addr) == 10000);
+ for (i = cands; i; i = i->next)
+ nice_candidate_free ((NiceCandidate *) i->data);
+ g_slist_free (cands);
+
+ cands = nice_agent_get_local_candidates (lagent, ls_id, 2);
+ g_assert (g_slist_length (cands) == 1);
+ cand = cands->data;
+ g_assert (cand->type == NICE_CANDIDATE_TYPE_HOST);
+ g_assert (nice_address_get_port (&cand->addr) == 10001);
+ for (i = cands; i; i = i->next)
+ nice_candidate_free ((NiceCandidate *) i->data);
+ g_slist_free (cands);
+
+ cands = nice_agent_get_local_candidates (ragent, rs_id, 1);
+ g_assert (g_slist_length (cands) == 1);
+ cand = cands->data;
+ g_assert (cand->type == NICE_CANDIDATE_TYPE_HOST);
+ g_assert (nice_address_get_port (&cand->addr) == 12345);
+ for (i = cands; i; i = i->next)
+ nice_candidate_free ((NiceCandidate *) i->data);
+ g_slist_free (cands);
+
+ cands = nice_agent_get_local_candidates (ragent, rs_id, 2);
+ g_assert (g_slist_length (cands) == 1);
+ cand = cands->data;
+ g_assert (cand->type == NICE_CANDIDATE_TYPE_HOST);
+ g_assert (nice_address_get_port (&cand->addr) == 10002);
+ for (i = cands; i; i = i->next)
+ nice_candidate_free ((NiceCandidate *) i->data);
+ g_slist_free (cands);
+
+ }
+
+ /* 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,
+ GUINT_TO_POINTER (1));
+ nice_agent_attach_recv (lagent, ls_id, NICE_COMPONENT_TYPE_RTCP,
+ g_main_loop_get_context (global_mainloop), cb_nice_recv,
+ GUINT_TO_POINTER (1));
+ nice_agent_attach_recv (ragent, rs_id, NICE_COMPONENT_TYPE_RTP,
+ g_main_loop_get_context (global_mainloop), cb_nice_recv,
+ GUINT_TO_POINTER (2));
+ nice_agent_attach_recv (ragent, rs_id, NICE_COMPONENT_TYPE_RTCP,
+ g_main_loop_get_context (global_mainloop), cb_nice_recv,
+ GUINT_TO_POINTER (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-drop-invalid: 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);
+ }
+
+ set_credentials (lagent, ls_id, ragent, rs_id);
+
+ /* step: pass the remote candidates to agents */
+ set_candidates (ragent, rs_id, lagent, ls_id, NICE_COMPONENT_TYPE_RTP);
+ set_candidates (ragent, rs_id, lagent, ls_id, NICE_COMPONENT_TYPE_RTCP);
+ set_candidates (lagent, ls_id, ragent, rs_id, NICE_COMPONENT_TYPE_RTP);
+ set_candidates (lagent, ls_id, ragent, rs_id, NICE_COMPONENT_TYPE_RTCP);
+
+ g_debug ("test-drop-invalid: 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: Send a packet from another address */
+ /* These should also be ignored */
+ {
+ NiceCandidate *local_cand = NULL;
+ NiceCandidate *remote_cand = NULL;
+ NiceSocket *tmpsock;
+
+ g_assert (nice_agent_get_selected_pair (lagent, ls_id, 1, &local_cand,
+ &remote_cand));
+ g_assert (local_cand);
+ g_assert (remote_cand);
+
+ tmpsock = nice_udp_bsd_socket_new (NULL);
+ nice_socket_send (tmpsock, &remote_cand->addr, 4, "ABCD");
+ nice_socket_send (tmpsock, &local_cand->addr, 5, "ABCDE");
+ nice_socket_free (tmpsock);
+ }
+
+ /* note: test payload send and receive */
+ global_ragent_read = 0;
+ ret = nice_agent_send (lagent, ls_id, 1, 16, "1234567812345678");
+ g_assert (ret != -1);
+ g_debug ("Sent %d bytes", ret);
+ g_assert (ret == 16);
+ while (global_ragent_read != 16)
+ g_main_context_iteration (NULL, TRUE);
+ g_assert (global_ragent_read == 16);
+
+ g_debug ("test-drop-invalid: Ran mainloop, removing streams...");
+
+ /* step: clean up resources and exit */
+
+ 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;
+
+#ifdef G_OS_WIN32
+ WSADATA w;
+
+ WSAStartup(0x0202, &w);
+#endif
+
+ global_mainloop = g_main_loop_new (NULL, FALSE);
+
+ /* step: create the agents L and R */
+ lagent = nice_agent_new (g_main_loop_get_context (global_mainloop),
+ NICE_COMPATIBILITY_RFC5245);
+ ragent = nice_agent_new (g_main_loop_get_context (global_mainloop),
+ NICE_COMPATIBILITY_RFC5245);
+
+ g_object_set (G_OBJECT (lagent), "ice-tcp", FALSE, NULL);
+ g_object_set (G_OBJECT (ragent), "ice-tcp", FALSE, NULL);
+
+
+ nice_agent_set_software (lagent, "test-drop-invalid, Left Agent");
+ nice_agent_set_software (ragent, "test-drop-invalid, Right Agent");
+
+ /* 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), GUINT_TO_POINTER(1));
+ g_signal_connect (G_OBJECT (ragent), "candidate-gathering-done",
+ G_CALLBACK (cb_candidate_gathering_done), GUINT_TO_POINTER (2));
+ g_signal_connect (G_OBJECT (lagent), "component-state-changed",
+ G_CALLBACK (cb_component_state_changed), GUINT_TO_POINTER (1));
+ g_signal_connect (G_OBJECT (ragent), "component-state-changed",
+ G_CALLBACK (cb_component_state_changed), GUINT_TO_POINTER (2));
+ g_signal_connect (G_OBJECT (lagent), "new-selected-pair",
+ G_CALLBACK (cb_new_selected_pair), GUINT_TO_POINTER(1));
+ g_signal_connect (G_OBJECT (ragent), "new-selected-pair",
+ G_CALLBACK (cb_new_selected_pair), GUINT_TO_POINTER (2));
+ g_signal_connect (G_OBJECT (lagent), "new-candidate",
+ G_CALLBACK (cb_new_candidate), GUINT_TO_POINTER (1));
+ g_signal_connect (G_OBJECT (ragent), "new-candidate",
+ G_CALLBACK (cb_new_candidate), GUINT_TO_POINTER (2));
+ g_signal_connect (G_OBJECT (lagent), "initial-binding-request-received",
+ G_CALLBACK (cb_initial_binding_request_received),
+ GUINT_TO_POINTER (1));
+ g_signal_connect (G_OBJECT (ragent), "initial-binding-request-received",
+ G_CALLBACK (cb_initial_binding_request_received),
+ GUINT_TO_POINTER (2));
+
+ g_object_set (G_OBJECT (lagent), "upnp", FALSE, NULL);
+ g_object_set (G_OBJECT (ragent), "upnp", FALSE, NULL);
+
+
+ /* step: run test the first time */
+ g_debug ("test-drop-invalid: TEST STARTS / running test for the 1st time");
+ result = run_full_test (lagent, ragent, &baseaddr, 4 ,0);
+ priv_print_global_status ();
+ g_assert (result == 0);
+ g_assert (global_lagent_state[0] == NICE_COMPONENT_STATE_READY);
+ g_assert (global_lagent_state[1] == NICE_COMPONENT_STATE_READY);
+ g_assert (global_ragent_state[0] == NICE_COMPONENT_STATE_READY);
+ g_assert (global_ragent_state[1] == NICE_COMPONENT_STATE_READY);
+ /* When using TURN, we get peer reflexive candidates for the host cands
+ that we removed so we can get another new_selected_pair signal later
+ depending on timing/racing, we could double (or not) the amount we expected
+ */
+
+ /* note: verify that correct number of local candidates were reported */
+ g_assert (global_lagent_cands == 2);
+ g_assert (global_ragent_cands == 2);
+
+ 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;
+}
diff --git a/tests/test-gstreamer.c b/tests/test-gstreamer.c
index 74d7133..f060efc 100644
--- a/tests/test-gstreamer.c
+++ b/tests/test-gstreamer.c
@@ -1,7 +1,7 @@
/*
* This file is part of the Nice GLib ICE library.
*
- * (C) 1015 Kurento.
+ * (C) 2015 Kurento.
* Contact: Jose Antonio Santos Cadenas
*
* The contents of this file are subject to the Mozilla Public License Version
@@ -34,7 +34,7 @@
*/
#include <gst/check/gstcheck.h>
-#include <nice/agent.h>
+#include "agent.h"
#define RTP_HEADER_SIZE 12
#define RTP_PAYLOAD_SIZE 1024
diff --git a/tests/test-mainloop.c b/tests/test-mainloop.c
deleted file mode 100644
index 7c52daa..0000000
--- a/tests/test-mainloop.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * This file is part of the Nice GLib ICE library.
- *
- * (C) 2006, 2007 Collabora Ltd.
- * Contact: Dafydd Harries
- * (C) 2006, 2007 Nokia Corporation. All rights reserved.
- * Contact: Kai Vehmanen
- *
- * 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:
- * Dafydd Harries, Collabora Ltd.
- * Kai Vehmanen, Nokia
- *
- * 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 <string.h>
-
-#include <nice/nice.h>
-#include "socket/socket.h"
-
-static GMainLoop *loop = NULL;
-
-static void
-recv_cb (
- NiceAgent *agent,
- guint stream_id,
- guint component_id,
- guint len,
- gchar *buf,
- gpointer data)
-{
- g_assert (agent != NULL);
- g_assert (stream_id == 1);
- g_assert (component_id == 1);
- g_assert (len == 6);
- g_assert (0 == strncmp (buf, "\x80hello", len));
- g_assert (42 == GPOINTER_TO_UINT (data));
- g_main_loop_quit (loop);
-}
-
-int
-main (void)
-{
- NiceAgent *agent;
- NiceAddress addr;
- guint stream;
-
- nice_address_init (&addr);
-
- loop = g_main_loop_new (NULL, FALSE);
-
- agent = nice_agent_new (g_main_loop_get_context (loop), NICE_COMPATIBILITY_RFC5245);
- nice_address_set_ipv4 (&addr, 0x7f000001);
- nice_agent_add_local_address (agent, &addr);
- stream = nice_agent_add_stream (agent, 1);
- nice_agent_gather_candidates (agent, stream);
-
- // attach to default main context
- nice_agent_attach_recv (agent, stream, NICE_COMPONENT_TYPE_RTP,
- g_main_loop_get_context (loop), recv_cb, GUINT_TO_POINTER (42));
-
- {
- NiceCandidate *candidate;
- GSList *candidates, *i;
-
- candidates = nice_agent_get_local_candidates (agent, 1, 1);
- candidate = candidates->data;
-
- nice_socket_send (candidate->sockptr, &(candidate->addr), 6, "\x80hello");
- for (i = candidates; i; i = i->next)
- nice_candidate_free ((NiceCandidate *) i->data);
- g_slist_free (candidates);
- }
-
- g_main_loop_run (loop);
-
- nice_agent_remove_stream (agent, stream);
- g_object_unref (agent);
-
- return 0;
-}
-
diff --git a/tests/test-nomination.c b/tests/test-nomination.c
new file mode 100644
index 0000000..bf21557
--- /dev/null
+++ b/tests/test-nomination.c
@@ -0,0 +1,263 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <gio/gio.h>
+#include <agent.h>
+
+static NiceComponentState global_lagent_state[2] = { NICE_COMPONENT_STATE_LAST, NICE_COMPONENT_STATE_LAST };
+static NiceComponentState global_ragent_state[2] = { NICE_COMPONENT_STATE_LAST, NICE_COMPONENT_STATE_LAST };
+static guint global_components_ready = 0;
+static gboolean global_lagent_gathering_done = FALSE;
+static gboolean global_ragent_gathering_done = FALSE;
+static int global_lagent_cands = 0;
+static int global_ragent_cands = 0;
+
+static gboolean timer_cb (gpointer pointer)
+{
+ g_debug ("test-nomination:%s: %p", G_STRFUNC, pointer);
+
+ /* signal status via a global variable */
+
+ /* note: should not be reached, abort */
+ g_error ("ERROR: test has got stuck, aborting...");
+
+ return FALSE;
+}
+
+static void cb_nice_recv (NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer user_data)
+{
+ g_debug ("test-nomination:%s: %p", G_STRFUNC, user_data);
+
+ /* XXX: dear compiler, these are for you: */
+ (void)agent; (void)stream_id; (void)component_id; (void)buf;
+
+ /*
+ * Lets ignore stun packets that got through
+ */
+ if (len < 8)
+ return;
+ if (strncmp ("12345678", buf, 8))
+ return;
+
+ if (component_id != 1)
+ return;
+}
+
+static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer data)
+{
+ g_debug ("test-nomination:%s: %p", G_STRFUNC, data);
+
+ if (GPOINTER_TO_UINT (data) == 1)
+ global_lagent_gathering_done = TRUE;
+ else if (GPOINTER_TO_UINT (data) == 2)
+ global_ragent_gathering_done = TRUE;
+}
+
+
+static void cb_component_state_changed (NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer data)
+{
+ gboolean ready_to_connected = FALSE;
+ g_debug ("test-nomination:%s: %p", G_STRFUNC, data);
+
+ if (GPOINTER_TO_UINT (data) == 1) {
+ if (global_lagent_state[component_id - 1] == NICE_COMPONENT_STATE_READY &&
+ state == NICE_COMPONENT_STATE_CONNECTED)
+ ready_to_connected = TRUE;
+ global_lagent_state[component_id - 1] = state;
+ } else if (GPOINTER_TO_UINT (data) == 2) {
+ if (global_ragent_state[component_id - 1] == NICE_COMPONENT_STATE_READY &&
+ state == NICE_COMPONENT_STATE_CONNECTED)
+ ready_to_connected = TRUE;
+ global_ragent_state[component_id - 1] = state;
+ }
+
+ if (state == NICE_COMPONENT_STATE_READY)
+ global_components_ready++;
+ else if (state == NICE_COMPONENT_STATE_CONNECTED && ready_to_connected)
+ global_components_ready--;
+ g_assert (state != NICE_COMPONENT_STATE_FAILED);
+
+ g_debug ("test-nomination: checks READY %u.", global_components_ready);
+}
+
+static void cb_new_selected_pair(NiceAgent *agent, guint stream_id,
+ guint component_id, gchar *lfoundation, gchar* rfoundation, gpointer data)
+{
+ g_debug ("test-nomination:%s: %p", G_STRFUNC, data);
+
+ if (GPOINTER_TO_UINT (data) == 1)
+ ++global_lagent_cands;
+ else if (GPOINTER_TO_UINT (data) == 2)
+ ++global_ragent_cands;
+}
+
+static void set_candidates (NiceAgent *from, guint from_stream,
+ NiceAgent *to, guint to_stream, guint component)
+{
+ GSList *cands = NULL, *i;
+
+ cands = nice_agent_get_local_candidates (from, from_stream, component);
+ nice_agent_set_remote_candidates (to, to_stream, component, cands);
+
+ for (i = cands; i; i = i->next)
+ nice_candidate_free ((NiceCandidate *) i->data);
+ g_slist_free (cands);
+}
+
+static void set_credentials (NiceAgent *lagent, guint lstream,
+ NiceAgent *ragent, guint rstream)
+{
+ gchar *ufrag = NULL, *password = NULL;
+
+ nice_agent_get_local_credentials(lagent, lstream, &ufrag, &password);
+ nice_agent_set_remote_credentials (ragent, rstream, ufrag, password);
+ g_free (ufrag);
+ g_free (password);
+ nice_agent_get_local_credentials(ragent, rstream, &ufrag, &password);
+ nice_agent_set_remote_credentials (lagent, lstream, ufrag, password);
+ g_free (ufrag);
+ g_free (password);
+}
+
+static void
+run_test(NiceNominationMode l_nomination_mode,
+ NiceNominationMode r_nomination_mode)
+{
+ NiceAgent *lagent, *ragent; /* agent's L and R */
+ const gchar *localhost;
+ NiceAddress localaddr;
+ guint ls_id, rs_id;
+ gulong timer_id;
+
+ localhost = "127.0.0.1";
+
+ /* step: initialize variables modified by the callbacks */
+ global_components_ready = 0;
+ global_lagent_gathering_done = FALSE;
+ global_ragent_gathering_done = FALSE;
+ global_lagent_cands = global_ragent_cands = 0;
+
+ lagent = nice_agent_new_full (NULL,
+ NICE_COMPATIBILITY_RFC5245,
+ l_nomination_mode == NICE_NOMINATION_MODE_REGULAR ?
+ NICE_AGENT_OPTION_REGULAR_NOMINATION : 0);
+
+ ragent = nice_agent_new_full (NULL,
+ NICE_COMPATIBILITY_RFC5245,
+ r_nomination_mode == NICE_NOMINATION_MODE_REGULAR ?
+ NICE_AGENT_OPTION_REGULAR_NOMINATION : 0);
+
+ 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);
+ nice_agent_set_software (lagent, "Test-nomination, Left Agent");
+ nice_agent_set_software (ragent, "Test-nomination, Right Agent");
+
+ timer_id = g_timeout_add (30000, timer_cb, NULL);
+
+ if (!nice_address_set_from_string (&localaddr, localhost))
+ g_assert_not_reached ();
+ nice_agent_add_local_address (lagent, &localaddr);
+ nice_agent_add_local_address (ragent, &localaddr);
+
+ g_signal_connect (G_OBJECT (lagent), "candidate-gathering-done",
+ G_CALLBACK (cb_candidate_gathering_done), GUINT_TO_POINTER(1));
+ g_signal_connect (G_OBJECT (ragent), "candidate-gathering-done",
+ G_CALLBACK (cb_candidate_gathering_done), GUINT_TO_POINTER (2));
+ g_signal_connect (G_OBJECT (lagent), "component-state-changed",
+ G_CALLBACK (cb_component_state_changed), GUINT_TO_POINTER (1));
+ g_signal_connect (G_OBJECT (ragent), "component-state-changed",
+ G_CALLBACK (cb_component_state_changed), GUINT_TO_POINTER (2));
+ g_signal_connect (G_OBJECT (lagent), "new-selected-pair",
+ G_CALLBACK (cb_new_selected_pair), GUINT_TO_POINTER(1));
+ g_signal_connect (G_OBJECT (ragent), "new-selected-pair",
+ G_CALLBACK (cb_new_selected_pair), GUINT_TO_POINTER (2));
+
+ g_object_set (G_OBJECT (lagent), "controlling-mode", TRUE, NULL);
+ g_object_set (G_OBJECT (ragent), "controlling-mode", FALSE, NULL);
+
+ ls_id = nice_agent_add_stream (lagent, 1);
+ rs_id = nice_agent_add_stream (ragent, 1);
+ g_assert (ls_id > 0);
+ g_assert (rs_id > 0);
+
+ /* Gather candidates and test nice_agent_set_port_range */
+ g_assert (nice_agent_gather_candidates (lagent, ls_id) == TRUE);
+ g_assert (nice_agent_gather_candidates (ragent, rs_id) == TRUE);
+
+ nice_agent_attach_recv (lagent, ls_id, NICE_COMPONENT_TYPE_RTP,
+ g_main_context_default (), cb_nice_recv, GUINT_TO_POINTER (1));
+ nice_agent_attach_recv (ragent, rs_id, NICE_COMPONENT_TYPE_RTP,
+ g_main_context_default (), cb_nice_recv, GUINT_TO_POINTER (2));
+
+ g_debug ("test-nomination: Added streams, running context until 'candidate-gathering-done'...");
+ while (!global_lagent_gathering_done)
+ g_main_context_iteration (NULL, TRUE);
+ g_assert (global_lagent_gathering_done == TRUE);
+ while (!global_ragent_gathering_done)
+ g_main_context_iteration (NULL, TRUE);
+ g_assert (global_ragent_gathering_done == TRUE);
+
+ set_credentials (lagent, ls_id, ragent, rs_id);
+
+ set_candidates (ragent, rs_id, lagent, ls_id, NICE_COMPONENT_TYPE_RTP);
+ set_candidates (lagent, ls_id, ragent, rs_id, NICE_COMPONENT_TYPE_RTP);
+
+ while (global_lagent_state[0] != NICE_COMPONENT_STATE_READY ||
+ global_ragent_state[0] != NICE_COMPONENT_STATE_READY)
+ g_main_context_iteration (NULL, TRUE);
+ g_assert (global_lagent_state[0] == NICE_COMPONENT_STATE_READY);
+ g_assert (global_ragent_state[0] == NICE_COMPONENT_STATE_READY);
+
+ nice_agent_remove_stream (lagent, ls_id);
+ nice_agent_remove_stream (ragent, rs_id);
+
+ g_source_remove (timer_id);
+
+ g_clear_object(&lagent);
+ g_clear_object(&ragent);
+}
+
+static void
+regular (void)
+{
+ run_test(NICE_NOMINATION_MODE_REGULAR, NICE_NOMINATION_MODE_REGULAR);
+}
+
+static void
+aggressive (void)
+{
+ run_test(NICE_NOMINATION_MODE_AGGRESSIVE, NICE_NOMINATION_MODE_AGGRESSIVE);
+}
+
+static void
+mixed_ra (void)
+{
+ run_test(NICE_NOMINATION_MODE_REGULAR, NICE_NOMINATION_MODE_AGGRESSIVE);
+}
+
+static void
+mixed_ar (void)
+{
+ run_test(NICE_NOMINATION_MODE_AGGRESSIVE, NICE_NOMINATION_MODE_REGULAR);
+}
+
+int
+main (int argc, char **argv)
+{
+ int ret;
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/nice/nomination/regular", regular);
+ g_test_add_func ("/nice/nomination/aggressive", aggressive);
+ g_test_add_func ("/nice/nomination/mixed_ra", mixed_ra);
+ g_test_add_func ("/nice/nomination/mixed_ar", mixed_ar);
+
+ ret = g_test_run ();
+
+ return ret;
+}
diff --git a/tests/test-restart.c b/tests/test-restart.c
index c2cbe9a..afc51b6 100644
--- a/tests/test-restart.c
+++ b/tests/test-restart.c
@@ -301,6 +301,11 @@ static int run_restart_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *
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);
+ /* This role switch request will be effective after restart. We test
+ * here that the role cannot be externally modified after conncheck
+ * has started. */
+ g_object_set (G_OBJECT (ragent), "controlling-mode", TRUE, NULL);
+ g_assert (ragent->controlling_mode == FALSE);
g_debug ("test-restart: Set properties, next running mainloop until connectivity checks succeed...");
@@ -329,10 +334,18 @@ static int run_restart_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *
global_ragent_read = 0;
g_assert (nice_agent_send (lagent, ls_id, 1, 16, "1234567812345678") == 16);
+ /* Both agent have a distinct role at the end of the conncheck */
+ g_assert (lagent->controlling_mode == TRUE);
+ g_assert (ragent->controlling_mode == FALSE);
/* step: restart agents, exchange updated credentials */
tie_breaker = ragent->tie_breaker;
nice_agent_restart (ragent);
g_assert (tie_breaker != ragent->tie_breaker);
+ /* This role switch of ragent should be done now, and both agents
+ * have now the same role, which should generate a role conflict
+ * resolution situation */
+ g_assert (lagent->controlling_mode == TRUE);
+ g_assert (ragent->controlling_mode == TRUE);
nice_agent_restart (lagent);
{
gchar *ufrag = NULL, *password = NULL;
@@ -375,6 +388,8 @@ static int run_restart_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *
/* note: verify binding requests were resent after restart */
g_assert (global_lagent_ibr_received == TRUE);
g_assert (global_ragent_ibr_received == TRUE);
+ /* note: verify that a role switch occured for one of the agents */
+ g_assert (ragent->controlling_mode != lagent->controlling_mode);
g_debug ("test-restart: Ran mainloop, removing streams...");