Skip to content

dialog: ignore replicated packets that hit another call's dialog id - #4398

Open
Lt-Flash wants to merge 1 commit into
OpenSIPS:masterfrom
Lt-Flash:fix/dialog-repl-id-collision
Open

Lt-Flash wants to merge 1 commit into
OpenSIPS:masterfrom
Lt-Flash:fix/dialog-repl-id-collision

Conversation

@Lt-Flash

Copy link
Copy Markdown

Summary

Stop dialog replication from applying one call's update, value and delete packets to a different call that got the same dialog id on another node.

Details

This is a bug fix for any dialog replication cluster where more than one node creates dialogs, such as several active nodes behind an anycast address. Issue #4397 has the full analysis and a reproduction.

Since b3c31fb the replication handlers find their dialog by (h_entry, h_id) only, which assumes the id is unique in the cluster. It is not: a node allocates the id on the initial INVITE but replicates the dialog only once it is answered, and every node raises its next_id past the ids it receives. So while a call rings on one node, another node can give a new call the same id. From then on:

  • update packets overwrite the other call's contacts, SDPs, state, flags, timeout and variables (topology hiding then sends its in-dialog requests to the wrong contact);
  • value packets replace its dialog values;
  • a delete packet ends it when the other call hangs up;
  • the create packet is skipped silently.

Solution

A new helper, dlg_repl_id_collision(), compares the Call-ID, and the caller tag when the packet carries one, of the dialog found by id with the packet. On a mismatch it logs

dialog id collision [<h_entry>:<h_id>]: ignoring replicated <create|update|value|delete> for callid |...| ftag |...|, local dialog has callid |...| ftag |...|

(without the From tags for value packets, which do not carry one) and the handler drops the packet. It is called:

  • in dlg_replicated_update() before the "not found, build a new dialog" branch, so no second dialog with the same id is created either;
  • in dlg_replicated_value(), on the Call-ID only, as value packets carry no tags;
  • in dlg_replicated_delete(), under the entry lock, since lookup_dlg() returns with the lock released and the legs array can be reallocated; the reference taken by lookup_dlg() is released on a mismatch;
  • in the "already created" branch of dlg_replicated_create(), which keeps skipping the packet but now logs the collision.

The check applies only to lookups by id (DLG_BIN_V4 and later; value packets always); older packets are already matched by Call-ID and tags. It does not affect the looping case b3c31fb was written for: looped dialogs share the Call-ID and From tag, so they pass the check and the id still picks the right one.

Two nodes can still give the same id to two calls; on the other nodes one of the two calls then has no replica, and an in-dialog request for it that reaches such a node is not matched. This PR does not change id allocation.

How I tested it: two nodes on one host as in #4397, with collisions forced on every run, comparing master 6abfdcc with this branch:

master 6abfdcc this branch
B's callee hangs up (the steps in the issue) BYE sent to A's caller BYE sent to B's caller
A hangs up after B ended (three timings) no dialog: B's delete packet ended A routed normally
dialog contents while both calls are up B holds A's caller contact and dialog value, A holds B's value each dialog keeps its own
packets for the other call applied, nothing logged logged and dropped (create, update, value, delete)
looping call (two dialogs, same Call-ID and tags) replicated and ended on both nodes the same, no collision logged
in-dialog BYE sent to the node that did not create the call routed from its replica the same

Builds: the whole tree with gcc 14.2.0 and -Werror (0 warnings), and the dialog module with clang 19.1.7 and -Werror.

Compatibility

No configuration, script or replication format change. Packets that belong to the dialog found by id always carry its Call-ID and caller tag, so they are applied as before. The only visible change is the new error line on a collision. There is no platform-specific code.

Closing issues

closes #4397

Since the replication format matches dialogs by their id, the update,
value and delete handlers find their dialog by (h_entry, h_id) only, but
the id is unique only on the node that created the dialog. Every node
raises its next_id past the ids it receives, and a dialog gets its id at
the initial INVITE while it is replicated only once answered. So two nodes
creating dialogs in the same hash entry while the first call is still
ringing can give both calls the same id, and the packets of one call are
then applied to the other: contacts and SDPs overwritten (a
topology-hidden BYE was sent to the other call's contact), dialog values
replaced, and a live dialog deleted when the other call hung up.

Check the Call-ID (and the caller tag, where the packet carries it) of the
dialog found by id, and log and drop the packet on a mismatch, as the CSeq
handler already does through match_dialog(). The create handler also logs
such a collision instead of treating the dialog as already created.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] dialog replication: packets of one call are applied to another call that got the same dialog id on another node

1 participant