Repository navigation
[BUG] presence_callinfo: line-seize accepted for an appearance already in an active call #4193
Description
OpenSIPS version you are running
version: opensips 3.6.7 (x86_64/linux)
flags: STATS: On, DISABLE_NAGLE, USE_MCAST, SHM_MMAP, PKG_MALLOC, Q_MALLOC, F_MALLOC, HP_MALLOC, F_PARALLEL_MALLOC, DBG_MALLOC, FAST_LOCK-ADAPTIVE_WAIT
ADAPTIVE_WAIT_LOOPS=1024, MAX_RECV_BUFFER_SIZE 262144, MAX_LISTEN 16, MAX_URI_SIZE 1024, BUF_SIZE 65535
poll method support: poll, epoll, sigio_rt, select.
git revision: eaee48e28e
main.c compiled on with gcc 10
Observed on 3.6.7. Master retains the missing per-index-state check and the unchecked
assignment/publish sequence in lineseize_subs_handl, but its outbound caller path differs,
so the reproduction below applies to 3.6.7 only. I am separately re-verifying master runtime
behaviour. This issue is independent of the inverted expiry test in lineseize_subs_handl,
corrected on master by 3595af48; the 3.6.7 reproduction below was run with the equivalent
correction applied.
Describe the bug
A line-seize SUBSCRIBE for an appearance index that is already in an active call is
accepted with 200 OK. This is the case the existing FIXME in lineseize_subs_handl
describes:
/* FIXME - check the seized idx is not already in a call */
/* do the seizing */
sca->seize_state = idx;The 3.6.7 reproduction reaches the handler because line seizure and per-index call state are
tracked separately. sca_set_calling_line() enters sca_set_line(), which, for the
still-valid seizure used in this reproduction, invokes terminate_line_sieze() and clears
seize_state. After the dialog confirms, its callback publishes that index as active. A
subsequent initial line-seize therefore skips the existing-seizure block because
seize_state == 0. With the per-index check named by the FIXME absent, the handler assigns
the index, changes its state to SCA_STATE_SEIZED, and publishes it.
Master has a different caller path. Commit 283b529c removed the exported
sca_set_calling_line(), introduced sca_engage(), and introduced sca_validate_call_out().
That validator unlocks the line without clearing the seizure; its only in-tree
terminate_line_sieze() invocation is a comment positioned after the unlock. Consequently,
sca_engage("A") does not perform the 3.6 call-time clear. Master retains the missing
per-index-state check and unchecked assignment/publish sequence, but I am re-verifying a
master-specific runtime trigger before making a reachability claim.
There is a second effect on the published state. set_sca_index_state() overwrites the
per-index entry in place (scai->state = state), so accepting the seize replaces active
with seized rather than conflicting with it — the in-progress call is no longer
represented in the published appearance state at all.
To Reproduce
With disable_dialog_support_for_sca=0 (internal publishing mode):
- Endpoint P1 sends a
line-seizeSUBSCRIBE forappearance-index=3→200 OK
(seize_state = 3). - P1 sends an INVITE from the line carrying
Call-Info: <line>;appearance-index=3.
sca_set_calling_line()terminates the seizure (seize_state → 0); the callee answers,
the dialog confirms, and index 3 becomesactive. - Endpoint P2 sends a
line-seizeSUBSCRIBE forappearance-index=3, while index 3 is in
the active call.
Observed:
p1_seize_status: 200
invite_status: 200
index_state_after_call: active
p2_seize_of_active_index_status: 200 <-- accepted while active
Both endpoints are software endpoints (sipp); no external SIP service is involved.
Expected behavior
A line-seize for an appearance that is already in an active call should not be granted,
and accepting a seize should not discard the appearance's existing call state in the
published NOTIFY.
Relevant System Logs
The published call-info NOTIFY after P2's accepted seize:
Call-Info: <sip:LINE>;appearance-index=3;appearance-state=seized;appearance-index=*;appearance-state=idle
Index 3 is published as seized only; the active state it held before P2's SUBSCRIBE is
gone, so watchers of the line no longer see the call in progress.
OS/environment information
- Operating System: Debian (container image)
- OpenSIPS installation: source, built from tag
3.6.7(eaee48e28e) - other relevant information:
presence_callinfoin internal publishing mode
(disable_dialog_support_for_sca=0); software endpoints only.
Additional context
We have a candidate patch that checks the index's current state before accepting the seize,
along with regression results, and are happy to open a PR if that shape is useful. We have
not done so because the right semantics look like a design decision rather than an obvious
fix — in particular whether a seize should be refused for every non-idle appearance, or only
for one in an active call. Seizing an appearance that is on hold appears to be a
legitimate way to resume or pick up that call, and deliberate barge-in is a real feature, so
a guard that rejects all non-idle states could break behaviour that is intended. Happy to
follow whichever direction you prefer.
Activity
liviuchircu commentedon Aug 12, 2026
Hey @briscocom! Nice find, and it looks like we should be able to reproduce it using a sipssert-opensips-tests test or two. And from there, we can start working on a fix.
briscocom commentedon Aug 12, 2026
Correcting the report above: the
sca_set_calling_line()→terminate_line_sieze()mechanism I described is the tested 3.6.7 path, not master's. Commit283b529cremoved the exportedsca_set_calling_line()and introducedsca_engage(); its caller-validation path does not perform the 3.6 call-time seizure clear. I have edited the body to scope the reproduction to 3.6.7.Master still contains the missing per-index-state check and unchecked assignment/publish sequence, but I am re-running a pinned, master-specific reproduction before claiming its runtime trigger or observable behaviour.
briscocom commentedon Aug 14, 2026
Follow-up as promised: the master-specific reproduction is done, pinned and MI-authoritative.
Method. OpenSIPS built from source at
3f76bcc, withopensips -Vreporting the same revision.All state is read from
presence_callinfo'slist_linesMI command — still in internal publishingmode (
disable_dialog_support_for_sca=0) —seize_state,seize_expiresas a tick delta, andper-index states, sampled immediately before and after the seize under test, each with a timestamp. Nothing is inferred from NOTIFYs. The four core cases run 20 times each; the
realistic-expiry variant and the two inbound probes run once.
The trigger on master is seizure expiry, not a cleared
seize_state.seize_stateis still setwhen the re-seize is granted — 1 at every sample, including the one taken immediately before the
grant. Once
seize_expireslapses, the handler takes its own"old seizing found, but it expired -- overwrite it" path and continues past the
FIXMEto assignand publish the index, whether or not that index is in a confirmed call.
The contrast that isolates it, 20 runs each:
activeseize_expires26–27activeseize_expires-2to-3seizedAt the moment of the re-seize both rows record the same
seize_state(1), the same index state(
active), the same index number, the same seize request (sameEvent,appearance-indexandExpires), and INVITE 200.seize_expiresis the onlydecision-relevant field that differs; the line name and the absolute
seize_expires_tsdiffer perrun, as they must. Both enter the same
seize_state != 0branch and diverge only at the expirycomparison, so the rejection in the first row follows from seizure liveness, and the index's own
call state is not consulted in either.
The call is live across the re-seize: a BYE on the original dialog returns 200 afterwards, and the
capture shows the confirmed INVITE / 200 / ACK preceding it.
Not an artefact of a short subscription. Repeated with
Expires: 20and a call outliving it, withthe same result.
Both flows reach it. The caller side via
sca_engage("A"), and the inbound side once the linealready exists in the hash from an earlier seize — the inbound call drives the index
active, and are-seize after the lapse clobbers it. With no prior seize the line is never created, so that case on
its own does not reproduce.
Reproduction available if it saves you time. One-command runner, pinned build, the MI sampling
above, both captures. I can send it over or open a PR against
sipssert-opensips-tests— I saw theplan to write tests there and it looked like there is no SCA scaffolding yet.
I also have a candidate guard — reject the seize when the target index is in an active call — which
turns both paths into 480 with the index left
active, and leaves the idle and live-seizure casesunchanged (20 runs each for those; the inbound path once). I am not proposing it as the fix: it keys
on
SCA_STATE_ACTIVEalone, so it does not coverSCA_STATE_HELD, and that semantics question isyours. The numbers are there if they are useful.
github-actions commentedon Aug 30, 2026
Any updates here? No progress has been made in the last 15 days, marking as stale. Will close this issue if no further updates are made in the next 30 days.
github-actions commentedon Sep 29, 2026
Marking as closed due to lack of progress for more than 30 days. If this issue is still relevant, please re-open it with additional details.
github-actions commentedon Oct 15, 2026
Any updates here? No progress has been made in the last 15 days, marking as stale. Will close this issue if no further updates are made in the next 30 days.