Skip to content

[BUG] presence_callinfo: line-seize accepted for an appearance already in an active call #4193

Open

Description

@briscocom

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):

  1. Endpoint P1 sends a line-seize SUBSCRIBE for appearance-index=3 → 200 OK
    (seize_state = 3).
  2. 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 becomes active.
  3. Endpoint P2 sends a line-seize SUBSCRIBE for appearance-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_callinfo in 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

  1. self-assigned this
    on Aug 12, 2026
  2. liviuchircu commented on Aug 12, 2026

    @liviuchircu
    Member

    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.

  3. briscocom commented on Aug 12, 2026

    @briscocom
    Author

    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. Commit 283b529c removed the exported sca_set_calling_line() and introduced sca_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.

  4. briscocom commented on Aug 14, 2026

    @briscocom
    Author

    Follow-up as promised: the master-specific reproduction is done, pinned and MI-authoritative.

    Method. OpenSIPS built from source at 3f76bcc, with opensips -V reporting the same revision.
    All state is read from presence_callinfo's list_lines MI command — still in internal publishing
    mode (disable_dialog_support_for_sca=0) — seize_state, seize_expires as a tick delta, and
    per-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_state is still set
    when the re-seize is granted — 1 at every sample, including the one taken immediately before the
    grant. Once seize_expires lapses, the handler takes its own
    "old seizing found, but it expired -- overwrite it" path and continues past the FIXME to assign
    and publish the index, whether or not that index is in a confirmed call.

    The contrast that isolates it, 20 runs each:

    case index state seizure result
    active call, seizure live active seize_expires 26–27 480
    active call, seizure expired active seize_expires -2 to -3 200, index → seized

    At 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 (same Event, appearance-index and
    Expires), and INVITE 200. seize_expires is the only
    decision-relevant field that differs; the line name and the absolute seize_expires_ts differ per
    run, as they must. Both enter the same seize_state != 0 branch and diverge only at the expiry
    comparison, 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: 20 and a call outliving it, with
    the same result.

    Both flows reach it. The caller side via sca_engage("A"), and the inbound side once the line
    already exists in the hash from an earlier seize — the inbound call drives the index active, and a
    re-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 the
    plan 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 cases
    unchanged (20 runs each for those; the inbound path once). I am not proposing it as the fix: it keys
    on SCA_STATE_ACTIVE alone, so it does not cover SCA_STATE_HELD, and that semantics question is
    yours. The numbers are there if they are useful.

  5. github-actions commented on Aug 30, 2026

    @github-actions

    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.

  6. github-actions commented on Sep 29, 2026

    @github-actions

    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.

  7. github-actions commented on Oct 15, 2026

    @github-actions

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions