Skip to content

B2BUA

OpenSIPS has many features but in the way it behaves when a media session is established, it is not more than a proxy, meaning that it only takes the messages from one side and passes them on the other side. However, this has proven not to be enough to provide certain services required for the server to be aware of the state of the sessions, monitor and control them. A Back-to-Back User Agent is exactly this, an entity in the SIP network which has the ability to control or start media sessions. The name comes from the behavior, since in fact what is required is for the B2BUA to stand in the middle and establish two dialogs with both endpoints that will eventually exchange media.

The B2BUA in OpenSIPS is an implementation of the behavior of a B2BUA as defined in RFC 3261 that offers the possibility to build certain services on top of it. It consists of two modules:

  • b2b_entities - the bottom half, implementing the basic functions of a UAC and UAS, by dealing with the actual network message exchange;
  • b2b_logic - the upper half, providing to the script and exterior the necessary functions for implementing the logic of the B2BUA services.

The two parts composing the B2BUA in OpenSIPS are bound to each other in a control-notify relationship. The b2b_logic module controls b2b_entities by sending commands and telling it what messages to send, while b2b_entities notifies b2b_logic when a request or a reply is received for a dialog that the B2B is handling.

B2B entities are internal OpenSIPS records corresponding to the dialogs in which the B2BUA is involved. More loosely, we use the term “entity” to refer to the SIP entity that is the peer user agent of OpenSIPS in these dialogs. Depending on how OpenSIPS acts for the first transaction (as a UAS or UAC), we have two types of entities:

  • b2b server entity, if it is created for a received initial INVITE;
  • b2b client entity, if OpenSIPS will have to start a dialog itself by sending an initial message.

There are two ways to trigger a new B2B session corresponding to a desired B2BUA scenario:

The initial details for the first entities that have to be connected are provided directly as parameters to the b2b_logic:trigger_scenario MI command. In the case of the b2b_init_request function, the B2B entity records have to be created beforehand with the b2b_server_new and b2b_client_new script functions.

Current OpenSIPS versions use namespaced MI commands for the B2B modules. The command used by this tutorial to start a call from the middle is b2b_logic:trigger_scenario. Other B2BUA MI commands are available for bridging, inspection and call termination:

Current MI commandModulePurpose
b2b_logic:trigger_scenariob2b_logicStart a new B2B scenario from MI.
b2b_logic:bridgeb2b_logicBridge an existing B2B call leg to a new destination.
b2b_logic:listb2b_logicList B2B logic sessions.
b2b_logic:terminate_callb2b_logicTerminate an ongoing B2B session.
b2b_entities:listb2b_entitiesList B2B entities.

Older OpenSIPS versions used flat MI command names for the same B2BUA operations. When adapting older examples to current OpenSIPS versions, use the namespaced commands above:

Current MI commandOlder MI command
b2b_logic:trigger_scenariob2b_trigger_scenario
b2b_logic:bridgeb2b_bridge
b2b_logic:listb2b_list
b2b_logic:terminate_callb2b_terminate_call
b2b_entities:listb2be_list

This mapping applies only to MI commands; script functions such as b2b_bridge() keep their script function names.

After initializing a B2B session, the call legs will be handled by the b2b_logic module and the first step will be to put the two initial entities in contact. Requests and replies belonging to these dialogs will not enter the script through the standard OpenSIPS routes but instead will be handled in b2b_logic dedicated routes, defined through the script_req_route and script_reply_route modparams or through the custom routes given as parameters to the b2b_init_request function. The further steps of the scenario can be implemented in these routes by calling specific b2b_logic script functions, such as b2b_bridge, in order to perform various actions. Normal “proxy-like” OpenSIPS functions should not be executed in the b2b_logic routes.

Some messages will be handled automatically by the module and will not enter the b2b_logic routes at all (BYE requests received while in the process of bridging two entities, ACKs/BYEs/replies for disconnected entities). Also, if no dedicated b2b_logic reply route is defined, replies will be handled internally by the module, with the same effects as calling the b2b_handle_reply function from such a route if it were defined.

The examples below use b2b_pass_request, b2b_send_reply, b2b_delete_entity and b2b_end_dlg_leg for the B2B route actions.

No scenario document must be defined for this usage case. This is a built-in mechanism and it can be requested to the B2B Logic by specifying the name “top hiding”. The functionality is a simple pass-through with the creation of a new dialog on the other side and transferring all the messages received on one side to the other side taking care only to change the dialog information.

Here is the theoretical expected message trace:

top hiding schema

This scenario can be used by a company for prepaid users to announce them at the beginning of the call what their credit is and at the end of the call what their remaining credit is.

What must happen at session level is:

  • the caller is connected to a media server
  • the caller is connected to the callee
  • if the callee hangs up the phone before the caller, the caller is connected to a media server (the same or other).

To establish these sessions the SIP message flow has to look like this (it is assumed that the same media server is used):

ppaid

The relevant part of an OpenSIPS config that implements this scenario is shown below:

modparam("b2b_logic", "script_req_route", "b2b_logic_request")
route {
...
if (is_method("INVITE") && !has_totag()) {
# choose an initial state and save it in context
$b2b_logic.ctx(state) = "1";
# save the original destination in context
$b2b_logic.ctx(callee_uri) = $tu;
# create the server entity
b2b_server_new("caller");
# create the initial client entity, to connect the caller to the media server
b2b_client_new("media", "sip:808@opensips.org");
# initialize B2B session for the "prepaid" scenario
b2b_init_request("prepaid");
exit;
}
...
}
route[b2b_logic_request] {
if ($rm != "BYE") {
# for requests other than BYE, no special actions need to be done,
# just pass the request to the peer
b2b_pass_request();
exit;
}
if ($b2b_logic.ctx(state) == "1" && $b2b_logic.entity(id) == "media") {
# if we are in the initial state (caller connected to media server)
# and the BYE is from the media server, we connect the caller with the callee
# end dialog with the media server
b2b_send_reply(200, "OK");
b2b_delete_entity();
# create the client entity corresponding to the callee
b2b_client_new("callee", $b2b_logic.ctx(callee_uri));
# trigger the bridge action
b2b_bridge("caller", "callee");
# save the next state in context
$b2b_logic.ctx(state) = "2";
} else if ($b2b_logic.ctx(state) == "2" && $b2b_logic.entity(id) == "callee") {
# if we are in the second state (caller is connected with the callee)
# and the callee hung up first, we connect the caller with the media server again
# end dialog with the callee
b2b_send_reply(200, "OK");
b2b_delete_entity();
# create a new client entity corresponding to the media server
b2b_client_new("media_end", "sip:808@opensips.org");
# trigger the bridging action
b2b_bridge("caller", "media_end");
# save the next state in context
$b2b_logic.ctx(state) = "3";
} else {
# a normal BYE request, for which no special scenario action is required,
# just pass the request to the peer
b2b_pass_request();
}
}

The last test case is called “Marketing”, because it suits very well to the requirements of a marketing campaign through phone. The company might choose a list of potential customers to call and announce about a new product/offer/discount. The message will be a recorded one, but the customer will also be offered the possibility to talk to a human operator. If the customer does not hang up the phone before the recorded message ends, it will be connected to a human operator. This is also a good means to filter the interested customers and make the process more efficient with less resources used.

The OpenSIPS B2BUA must connect two endpoints, start the call from the middle. It is different from the other examples, since the call is initiated by the B2BUA and it is triggered by a MI command.

Below is the theoretical message flow that should occur for this functionality to be achieved.

marketing

The relevant part of an OpenSIPS config that implements this scenario is shown below:

modparam("b2b_logic", "script_req_route", "b2b_logic_request")
route[b2b_logic_request] {
if ($rm != "BYE") {
# for requests other than BYE, no special actions need to be done,
# just pass the request to the peer
b2b_pass_request();
exit;
}
if ($b2b_logic.ctx(state) != "2" && $b2b_logic.entity(id) == "media") {
# if we are in the initial state (customer connected to media server)
# and the BYE is from the media server, we connect the customer with the operator
# end dialog with the media server
b2b_send_reply(200, "OK");
b2b_delete_entity();
# create the client entity corresponding to the operator,
# take the URI of the destination from the context
b2b_client_new("operator", $b2b_logic.ctx(operator_uri));
# trigger the bridging action
b2b_bridge("customer", "operator");
# save the next state in context
$b2b_logic.ctx(state) = "2";
} else {
# a normal BYE request, for which no special scenario action is required,
# just pass the request to the peer
b2b_pass_request();
}
}

MI command example:

Terminal window
opensips-cli -x mi b2b_logic:trigger_scenario marketing customer,sip:bob@opensips.org media,sip:808@opensips.org:5070 operator_uri=sip:alice@opensips.org

The B2BUA component can also be configured to handle blind transfers (with the REFER SIP message). This is useful when in the platform there are devices that do not accept the REFER method. The scenario for this case is very simple and has one rule for when a REFER is received. This rule tells the B2BUA to end the call with the user agent that sent the REFER message and to bridge the peer with the user specified in the ‘Refer-To’ header.

The relevant part of an OpenSIPS config that implements this scenario is shown below:

modparam("b2b_logic", "script_req_route", "b2b_logic_request")
route {
...
if (is_method("INVITE") && !has_totag()) {
# create the server entity
b2b_server_new("caller");
# create the initial client entity, to connect the caller with the callee
b2b_client_new("callee", $ru);
# initialize B2B session for the "refer" scenario
b2b_init_request("refer");
exit;
}
...
}
route[b2b_logic_request] {
if ($rm != "REFER") {
# for requests other than REFER, no special actions need to be done,
# just pass the request to the peer
b2b_pass_request();
exit;
}
# end dialog with the referrer
b2b_send_reply(202, "Accepted");
b2b_end_dlg_leg();
# create the client entity corresponding to
# the user specified in the 'Refer-To' header
b2b_client_new("referee", $hdr(Refer-To));
# bridge the referrer's peer with the referee
b2b_bridge("peer", "referee");
}