Back to Knowledge Hub
Fingerprint

WebRTC ICE Candidate Pooling and Privacy Timing

Separate ICE prefetch, candidate delivery and restart phases; a missing page event does not establish that earlier network activity did not occur.

BotBrowser Team

Documentation

Want the structured docs for Fingerprint?

This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.

The ICE candidate pool changes when some work may happen; it does not make gathering, page events, local description and a later restart the same observation. A quiet event log cannot establish that no earlier server-facing activity occurred. BotBrowser documents WebRTC address policies, but does not document a pool-size control or reversal of prior network activity.

Four WebRTC phases: optional pre-gathering, candidate delivery during local description, first-exchange pool consumption, and a later restart that begins a new negotiation

TL;DR

  • A nonzero iceCandidatePoolSize requests pre-gathering; it does not prove the pool is ready.
  • setLocalDescription() is a candidate-delivery phase, not a timestamp for the first network activity.
  • The initial pool serves the first exchange; restartIce() requests later negotiation and does not rewind earlier activity.

Watch the video

This English-language video follows the same question as the article: a WebRTC trace holds several clocks, so a configured pool size, a page candidate event, a local description and a server log cannot be swapped for one "ICE started" instant. It shows a controlled local fixture in Ubuntu Chrome, where a peer connection is built with a nonzero pool size and a later change returns InvalidModificationError, plus a second probe that counts candidate events inside a stated window and reads the gathering state. The count stays a page observation, not a server-side record, and the video does not claim that any real site was measured or that BotBrowser controls pool size. The audio is English.

Contents

BotBrowser can apply documented WebRTC address policies in controlled sessions, but its public documentation does not promise a candidate-pool-size control or reversal of earlier activity. The WebRTC leak-prevention guide explains candidate types and route policy; the mDNS candidate guide explains host-candidate representation. The narrower question here is when each ICE phase can be observed.

Why timing changes the privacy question

A WebRTC trace can contain several clocks: construction of the peer connection, optional candidate prefetch, a call to set a local description, candidate events delivered to the page, an offer/answer exchange, and a later ICE restart. Treating all of those as one instant called “ICE started” hides distinctions that matter when interpreting a privacy report. The WebRTC Recommendation defines iceCandidatePoolSize as the size of a prefetched pool and gives it a default of zero. A nonzero value requests a pool; it is not a timestamped record of what the page received.

API boundaryRecordDo not infer
RTCPeerConnection(configuration)Requested iceCandidatePoolSize and capture startThat pre-gathering completed
setLocalDescription()Call time and delivered candidate eventsThat network activity began at that call
restartIce()Restart request and next negotiationThat the initial pool was restored

The distinction matters because a page event describes an application-visible delivery point. It does not timestamp every DNS, STUN or TURN operation that might have happened earlier. The inverse also matters: if an application log contains no candidate event, that absence alone does not establish that no gathering or server-facing activity occurred. The capture could have started late, the application could have filtered its own record, or the observation could come from a different layer. Those possibilities are reasons to mark the evidence boundary, not permission to invent a missing event.

For an authorized compatibility or privacy review, keep four observations separate:

  • The configured pool size and when that configuration was captured.
  • Network activity seen by an independently authorized server-side record, if one exists.
  • Candidate events delivered to the application, with the capture window stated.
  • The local description and offer/answer phase recorded by the application.

These records answer different questions. A setting says what the application requested. A page event says what was delivered to script. A local description says what the application supplied to the negotiation API. A server record says what that particular server observed. None of these alone proves that an address policy preceded every relevant network action. The evidence should identify its clock and vantage point before two timestamps are compared.

The pool is also not a candidate count visible to the page. A configured size does not establish that the implementation finished gathering that number of usable candidates before a later call. RFC 9429 explicitly accounts for a pool that is depleted because more ICE components are needed or because gathering has not had enough time. In that case, remaining candidates are gathered normally. Therefore the configured value, the number of page events, and the contents of a local description are not interchangeable measurements.

This separation helps avoid two common overstatements. “No candidate event was logged before the local description” is a statement about a particular page log and its coverage. “No network activity happened before the local description” is a broader statement requiring evidence from the relevant network vantage point. Similarly, a nonzero pool setting permits prefetch behavior; it does not by itself prove that a particular server was contacted at a particular time. Record what each source can actually show and keep the rest as unknown.

The pool has a bounded lifecycle

The candidate pool has a defined role in negotiation; it is not a cache that is reused for every later exchange. RFC 9429 section 3.5.4 describes the sequence: when setLocalDescription begins the gathering needed for the first offer/answer exchange, the implementation should first check for candidates already available in the pool. Available candidates should be handed to the application through candidate events. If the pool is depleted because the exchange needs more components or the pool has not had enough time to gather candidates, the rest are gathered as usual. RFC 9429 says this pool behavior applies to the first offer/answer exchange; after that exchange the pool is emptied and is no longer used.

The RFC uses “SHOULD” for delivery of available pooled candidates. Preserve that modality: it is a standards recommendation, not evidence that a particular pool completed before a particular wall-clock time. The same section explains that holding pre-gathered candidates alive can consume resources on the configured STUN or TURN servers. That is a resource consequence stated by the RFC, not a measured traffic volume or a promise about retention duration in a specific browser build.

Configuration timing is constrained too. The WebRTC set-configuration algorithm rejects a changed iceCandidatePoolSize once setLocalDescription has already been called. The method reports InvalidModificationError for that restricted change, but the exception name is not a diagnosis: other configuration changes can produce the same exception. A useful incident record needs the old and requested configurations, the order of API calls, and the exception context. Without those, attributing the exception to pool size is only a guess.

PhasePool interpretationApplication-visible evidenceWhat the record cannot establish
Construction with size 0This setting requested no prefetchInitial configuration snapshotThat no later gathering will happen
Construction with nonzero sizePrefetch is permitted while candidates are preparedRequested size; separate server observation if availableThat the pool filled or a page event already fired
Before local descriptionThe size can be selected or changed within API rulesConfiguration snapshots and call orderThat a requested pool is ready
First local-description gatheringAvailable pooled candidates should be delivered; shortages gather normallyCandidate events and local-description lifecycleThe complete earlier network timeline from page events alone
After the first exchangeThe initial pool is empty and no longer usedNegotiation historyThat a later restart recreates the prefetched pool
Later ICE restartA new ICE generation is requested through negotiationRestart call and subsequent signalingThat prior activity has been erased

The matrix is an interpretation aid for records from an owned application, not a detector recipe. Capture phase labels and coarse ordering rather than candidate contents. Keep the distinction between a configuration request and a completed operation. If the capture has no independent network observation, say so; do not convert the page event stream into a proxy for server activity.

An ICE restart is a later negotiation action, not a rewind button. The restartIce() API requests an ICE restart for a subsequent negotiation. It does not retroactively remove an earlier event, erase a server log, or restore the initial prefetch pool after the first offer/answer exchange has consumed it. If a report spans more than one generation, label which phase each record covers. Otherwise, a candidate observed after restart can be incorrectly joined to evidence from the initial exchange.

Keep the observation layers separate

An evidence bundle should state which layer produced each observation and which time interval it covers. A configuration snapshot records the application's requested values. A page event records delivery to script. A local description records the data supplied at that API boundary. A server log, when the system owner is authorized to inspect it, records what that server observed. Their clocks may differ, their retention may differ, and their coverage may begin at different times. A comparison that omits these facts can look more conclusive than the underlying records warrant.

ObservationIt supportsIt does not support by itself
iceCandidatePoolSize: 0 before negotiationThat this setting did not request prefetchThat candidates cannot be gathered later
A nonzero configured poolThat prefetch was requestedThat gathering completed or the pool remained available
A candidate event timestampThat the page received that event at the recorded timeThat the first network activity occurred then
A setLocalDescription callThat the application entered the local-description phaseThat all candidates had already been gathered
A restartIce() callThat the application requested restart for subsequent negotiationThat the initial pool was restored or earlier activity erased
An absent event in a partial traceThat the trace does not contain that eventThat no activity occurred outside the capture window

A practical record can preserve the capture start time, ordered API phase markers, browser version, and whether the log is complete. The purpose is to interpret authorized diagnostics, not to collect more data than the diagnosis needs. Raw candidate strings, addresses, SDP bytes, server credentials and per-user identifiers are not necessary for the lifecycle distinctions in this article. If a separately authorized incident requires sensitive data, handle it under that system's retention and access policy rather than copying it into a general report.

Avoid joining a page event time to an assumed STUN request time when there is no independent network observation. Such a join may be useful as a hypothesis to investigate, but it is not an observed fact. The same caution applies to an export pipeline that drops fields: a field absent from an export can mean that the exporter omitted it, not that the browser never produced it. Preserve provenance for each record: source layer, capture interval, transformation steps and the point at which the record was retained.

For repeatable comparison, use the same observation definitions in both runs. If one run records API calls from connection construction and another starts logging at the first candidate callback, their apparent timelines are not aligned. Make a small table with the event definition, clock source, start boundary and missing-data rule before comparing outcomes. This is a review method for owned test systems; it does not require exposing candidate values or probing third-party endpoints.

Triage an incomplete timeline

When a report describes an address as “late,” define what “late” means before looking for a cause. It could mean the first candidate event seen by page code, the first candidate represented in the local description, the first event retained by an export process, or the first observation in an authorized server log. These timestamps are not interchangeable. First check whether capture began before the peer connection was constructed and whether the initial configuration was recorded. Then check whether the application had already called setLocalDescription when it attempted to change the pool size.

SymptomCheck firstAvoid concluding
No candidate event before local descriptionWhether collection was requested and whether the capture covers earlier activityThat there was no prefetch or server contact
Fewer events than expected after local descriptionWhether the pool had enough capacity or preparation time; ordinary gathering may continueThat the connection failed solely because the pool was small
InvalidModificationError during configurationWhich field changed and whether setLocalDescription had already been calledThat pool size was necessarily the rejected field
Events after restartIce()The restart call, renegotiation order and capture completenessThat the prior generation or its activity disappeared
Server and page records disagreeClock source, capture bounds, filtering and observation vantageThat either record describes the other layer

This sequence keeps diagnosis tied to the evidence. Start with call order and capture scope. Compare the requested value with the value actually recorded before the local description. Separate configuration exceptions from event delivery. Only then compare page and server records if both were collected under authorization. If an item is missing, record the gap rather than filling it with an assumed default or a guessed event time.

If the initial configuration or capture interval is unavailable, mark the timeline incomplete. Do not reconstruct the original intent from a final candidate list. A final list is a retained result, not a complete chronology of how the implementation reached it. If the export has no provenance, say that the transformation history is unknown. This makes the report less absolute but more useful: another engineer can see which missing fact would change the interpretation.

When comparing two runs, differences may come from capture coverage, network response timing, application call order, or server logging boundaries, as well as the configured pool size. Record those conditions alongside the comparison. Do not claim that a pool-size change caused a difference unless the evidence isolates that change and the applicable source supports the proposed mechanism. The cited standards describe API behavior and lifecycle; they do not supply measurements for an unrun browser configuration.

The same method applies when a configuration change fails. Preserve the complete old and new configuration objects with secrets removed, note whether local description was already called, and retain the exact exception type and phase. Then consult the algorithm for the changed fields. InvalidModificationError tells you that the attempted change was not accepted under the API rules; it does not say which field was responsible without the surrounding record. A narrow conclusion avoids sending an engineer toward an unrelated network hypothesis.

BotBrowser policy boundary

BotBrowser's WebRTC documentation describes address-policy choices and configuration for managed sessions. It is the product source for those controls. The default pool value, its first-exchange lifecycle, and the timing of local-description processing come from the WebRTC Recommendation and JSEP. The public product documentation does not promise a dedicated pool-size control, that every candidate will be prefetched, or that changing a policy later removes earlier server-side activity.

For a controlled comparison, record the selected product policy separately from API phase evidence and attribute each statement to its source. Do not treat a configuration reference as a network-side proof. Do not treat a missing page event as proof that the host never contacted a configured service. The WebRTC leak-prevention guide remains the operational reference for address-policy setup; the lifecycle tables here add an interpretation layer for time and evidence scope. No result about an unexecuted browser build, network path, or deployment is claimed.

FAQ

Does a nonzero pool size guarantee candidates are ready before negotiation?

No. It requests pre-gathering, but RFC 9429 describes cases where the pool is depleted or gathering has not had enough time. Available pooled candidates should be delivered as gathering begins; the setting is not a completion receipt.

Can changing the pool size after local description fix an earlier capture?

No. The W3C set-configuration algorithm restricts that change after setLocalDescription() has been called. A later configuration cannot rewrite a previous event log or server observation.

Does restartIce() restore the initial pool?

No. It requests an ICE restart for a subsequent negotiation. The restart has its own evidence interval and does not make earlier network activity disappear.

Sources

Practical conclusion

Keep configuration, page events and server observations as separate evidence. Preserve the capture window and call order; when either is missing, report the timeline as incomplete instead of inferring that no earlier network activity occurred.

#WebRTC#ICE#Privacy#Network#Fingerprinting

This article is part of the guide: What Is a WebRTC IP Leak? Prevention and Browser Privacy

Take BotBrowser from research to production

The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.