Engage Bridging Service - rallytac/pub GitHub Wiki
Engage's groups are channels of communication between teams of users carrying out specific job functions. Most of the time those communications stay within the group. Sometimes you need to combine groups so users can talk across them. That is what the Engage Bridging Service—EBS—does.
This process of "bridging" is sometimes also called "patching" or "cross-banding". They all basically mean the same thing.
EBS is a "headless" application that runs on a variety of platforms and has a number of similarities to Rallypoints. If you're already familiar with Rallypoints, you're well on your way to understanding EBS.
The major difference: Rallypoints route packets between users (endpoints); EBS routes traffic between Engage groups (channels). To do that, EBS uses an embedded Engage Engine.
Technically, EBS is a headless Engage-powered application that instructs its underlying Engage Engine to perform the bridging operations needed.
- Prerequisites
- Installation
- Operation
- Configuration
- Licensing & the Device Identifier
- Bridging
- Monitoring Tools
- Examples
EBS runs on Linux, macOS, and Microsoft Windows—with Linux preferred. For Linux, use a current Red Hat–family distribution (RHEL, Rocky Linux, AlmaLinux, or similar) or a Debian-family distribution (Ubuntu LTS or Debian stable). Older releases such as CentOS 7 or Ubuntu 18.04 may still run packaged builds, but newer LTS/stable releases are recommended.
EBS is not a classic “clients connect to me” server the way a Rallypoint is, but it still listens for TCP health-check connections when load balancers or other infrastructure probe process availability. Open the health-check port you configure. That port typically carries little or no application traffic—checkers open a TCP connection and close it or hold it briefly. DoS detection may need tuning for fast connect/disconnect cycles.
Because EBS runs an Engage Engine, groups that use UDP multicast need firewall rules for multicast RX/TX and the relevant UDP ports. For groups that attach via Rallypoints, the EBS host must be able to reach out to those Rallypoints (TCP 7443 by default, plus UDP 7444 if UDP streaming is enabled on the RP path).
EBS is most easily installed with the package manager using the package from Rally Tactical Systems. Packages install binaries, factory-default certificates, and a baseline configuration, and set EBS up as a daemon at boot—systemd on Linux, launchd on macOS.
For Red Hat–family distributions:
sudo yum install <ebs_package_file>.rpm
For Debian-based distributions:
sudo apt install <ebs_package_file>.deb
NOTE: To install from a local file (not a repo package name),
cdto the directory containing the file and precede the name with./.
For macOS, open the <ebs_package_file>.dmg and double-click the install icon.
For a custom layout or foreground runs, install by hand:
- Place the
engagebridgedexecutable where it can be run (for example /usr/sbin). - Place certificate/key material where only EBS and authorized users can read it.
- Place the service configuration where EBS can read it. Default:
/etc/engagebridged/engagebridged_conf.json. - Place the bridging configuration (groups + bridges) where the service conf points—packaged default is often
/etc/engagebridged/bridges.json.
Override the service config path with -cfg:
$ engagebridged -cfg:my_custom_configuration.jsonOn Linux, wire systemd (or legacy init) per your OS docs. On macOS, see Apple’s launchd documentation:
Once installed and configured, EBS starts and processes bridging configurations. Under systemd:
| Operation | Command Line |
|---|---|
| Starting the service | sudo systemctl start engagebridged |
| Stopping the service | sudo systemctl stop engagebridged |
| Restarting the service | sudo systemctl restart engagebridged |
| Query service status | sudo systemctl status engagebridged |
| Watch the log | sudo journalctl -f -u engagebridged |
From the command line, logs go to the terminal; stop with Ctrl-C or kill.
View logs in the terminal or via journalctl. Output also goes to the OS logging subsystem (syslog on Linux; unified logging on macOS). Messages follow syslog-style severity. Tools such as SolarWinds or Papertrail can alert on them. In ANSI-capable terminals, lines are colorized for easier scanning.
EBS can write a periodic JSON status file (we recommend 30-second intervals, practical minimum about 5 seconds). Analyze that file for health.
Here's an illustrative example (// comments are explanatory only—not valid in real JSON):
{
"id":"demorp0001", // The EBS process' instance identifier
"ts":119790397, // UTC UNIX timestamp when this report was produced
"uptime": 149663, // Seconds the process has been up
"systemCpuLoad":4.86, // Host CPU load percentage
"healthChecks":
{
"count":0,
"rate":0.0,
"rateEma":0.0
},
"queue":
{
"avgExecNanos":60567,
"maxExecNanos":7733634,
"minExecNanos":0,
"lowPriorityQueueDepth":0,
"lowPriorityQueueMaxDepth":0,
"lowPriorityQueueFailures":0,
"normalPriorityQueueDepth":0,
"normalPriorityQueueMaxDepth":1,
"normalPriorityQueueFailures":0,
"ops":
{
"count":3360,
"rate":145.0,
"rateEma":15.002
},
"spuriousWakeups":114,
"wakeUps":3346
},
"bridges":
{
"count":2,
"detail":
[
{
"id":"1+2",
"name":"Test #1",
"state":0,
"groups":
[
"{ac197b82-0f45-86e8-bf53-02ceea2e977c}",
"{f38a3c6f-201c-648e-4dbe-1e8565afe0c5}"
]
},
{
"id":"2+3",
"name":"Test #2",
"state":0,
"groups":
[
"{f38a3c6f-201c-648e-4dbe-1e8565afe0c5}",
"{d54e51d0-1cc1-e130-f955-35b8f763cf9b}"
]
}
]
},
"groups":
{
"count":4,
"detail":
[
{
"id":"{ac197b82-0f45-86e8-bf53-02ceea2e977c}",
"name":"Group 1",
"state":0
},
{
"id":"{d54e51d0-1cc1-e130-f955-35b8f763cf9b}",
"name":"Group 3",
"state":0
},
{
"id":"{eef5ae84-bc51-941b-43e8-fde506415ef3}",
"name":"Group 4",
"state":0
},
{
"id":"{f38a3c6f-201c-648e-4dbe-1e8565afe0c5}",
"name":"Group 2",
"state":0
}
]
}
}| Code | Description |
|---|---|
| -1 | An error has occurred |
| 0 | No status available |
| 1 | Bridge is operational |
| 2 | Bridge is in the process of being created |
| 3 | Bridge has been deleted |
| Code | Description |
|---|---|
| -1 | An error has occurred |
| 0 | No status available |
| 1 | Group is operational |
| 2 | Group is in the process of being created |
| 3 | Group is attempting to join the network |
| 4 | Group has left the network |
| 5 | Group is temporarily disconnected |
| 6 | Group has been deleted |
Now that you've seen how to install, operate, and monitor the software, configure it.
Safeguard X.509 certificate files and especially private keys. Certificates are ultimately public; private keys are PRIVATE. Only EBS and authorized entities should read those files.
EBS uses a service JSON file at /etc/engagebridged/engagebridged_conf.json (or -cfg). The shipped file is the authoritative full schema. Bridging topology (which groups sit in which bridges) lives in a separate file—typically /etc/engagebridged/bridges.json—see Bridging.
Illustrative service config (comments not valid in real JSON):
{
"id":"bridgeserver01",
"mode":0,
"certStoreFileName":"/etc/engagebridged/engagebridged.certstore",
"certStorePasswordHex":"",
"bridgingConfigurationFileName":"/etc/engagebridged/bridges.json",
"bridgingConfigurationFileCommand":"",
"bridgingConfigurationFileCheckSecs":30,
"serviceConfigurationFileCheckSecs":30,
"configurationCheckSignalName":"rts.6cc0651.${id}",
"statusReport":
{
"enabled":true,
"fileName":"/tmp/${id}_status.json",
"intervalSecs":30,
"includeGroupDetail":true,
"includeBridgeDetail":true,
"includeBridgeGroupDetail":true,
"runCmd":""
},
"statusUpload":
{
"baseUrl":"",
"timeoutSecs":3
},
"externalHealthCheckResponder":
{
"listenPort":0,
"immediateClose":true
},
"fipsCrypto":
{
"enabled":false,
"path":"",
"debug":false,
"curves":"",
"ciphers":""
},
"enginePolicy":
{
"licensing":{
"entitlement": "<an_entitlement_is_required_to_run>",
"key":"<a_license_key_is_required_to_run>",
"activationCode":"<an_activation_code_may_be_required>",
"deviceId":"",
"manufacturerId":"<add_your_manufacturer_id_if_you_have_one>"
},
"featureset":{
"signature":"",
"lockToDeviceId":false,
"features":[]
},
"networking":
{
"defaultNic":"",
"requireMulticast":true,
"rpUdpStreaming":
{
"enabled":false,
"port":0,
"keepaliveIntervalSecs":15,
"priority":3,
"ttl":64
}
},
"security":
{
"certificate":
{
"certificate":"@certstore://rtsFactoryDefaultEngage",
"key":"@certstore://rtsFactoryDefaultEngage"
},
"caCertificates":[]
}
}
}-
id— unique EBS instance ID. Blank ⇒ auto-generated; set it explicitly (hostname or cloud/container ID works well). -
certStoreFileName/certStorePasswordHex— certificate store path and hex passphrase. See Engage Security. -
bridgingConfigurationFileName— path to the bridges/groups JSON (packaged default often/etc/engagebridged/bridges.json). -
bridgingConfigurationFileCommand— OS command that prints bridging JSON to STDOUT instead of reading a file. Must finish quickly (< 30s) or EBS may kill it. -
bridgingConfigurationFileCheckSecs— poll interval for the bridging file/command (sample often30; code default60). -
serviceConfigurationFileCheckSecs— interval for re-checking the service configuration for changes (default60). -
configurationCheckSignalName— POSIX semaphore name; signaling it wakes a config check. Defaultrts.6cc0651.${id}. Empty disables signalled reload.
IMPORTANT:
bridgingConfigurationFileNameandbridgingConfigurationFileCommandare mutually exclusive. Setting both aborts startup.
Default bridging operation mode for the whole service:
| Value | Name | Behavior |
|---|---|---|
0 |
Raw (default) | Forward group packets without inspecting/modifying payloads. Groups are treated as raw. |
1 |
Multistream | Transform audio payloads; headers preserved; parallel output streams possible. Forces groups to audio (type = 1). |
2 |
Mixed stream | Mix audio to anonymous output (no metadata even if targets allow header extensions). Forces audio groups. |
3 |
Dictated by group | Do not force group type / bridgeTargetOutputDetail; each group’s own settings win. |
Payload/audio modes typically need the correct licensing and enginePolicy.featureset entitlements. See Payload Processing Mode.
-
enabled— defaultfalse -
fileName— status JSON path;${id}supported -
intervalSecs— recommend ≥ 5s; 30s is common -
includeGroupDetail— include group list/detail -
includeBridgeDetail— include bridge list/detail -
includeBridgeGroupDetail— when bridge detail is on, include each bridge’s group membership -
runCmd— shell command after each write (${fn}= path). EBS executes this as the EBS user—treat as a privileged hook.
HTTP POST of status when baseUrl is non-empty (timeoutSecs default 3). Useful for central ingest; leave empty to disable.
-
listenPort— TCP port for LB/health probes (0= disabled) -
immediateClose— close immediately when true
Same pattern as Rallypoint / Engine FIPS 140-3 (OpenSSL 3.1.2 FIPS Provider, CMVP #4985): enabled, path (directory containing rts-fips), optional debug, curves, ciphers. Also selectable via -fips where supported. See Engage Security.
Service-level internals (distinct from enginePolicy.internals):
-
watchdog— hang detection (enabled,intervalMs,hangDetectionMs,abortOnHang,slowExecutionThresholdMs).abortOnHangkills the process on hang. -
housekeeperIntervalMs— housekeeping tick (default1000) -
nsmUnhealthyBridgeGraceMs— base grace before releasing an NSM-gated bridge that is not healthy (default5000). When a bridged group uses a multi-RPrallypointClusterand is still connecting (joined/disconnected, not a hard error), EBS extends this to one full Leaf rollover cycle (rolloverSecs ×number of RPs) so ownership is not dropped before other RPs can be tried. Hard failures (group/bridge error, left, missing group) still use the base grace. -
nsmResourceReleaseCooldownMs— cooldown after releasing an NSM resource (default30000) -
tuning— memory/object pool caps (zeros ⇒ practical defaults). Leave alone unless measuring memory pressure.
Embedded Network State Machine for electing which EBS instance owns which bridge resources in an HA pair/cluster.
Unlike Rallypoint (which currently only parses nsm), EBS runs embedded NSM when configured. Typical shape:
- Shared
nsm.statusReportfor NSM-specific status -
nsm.nodes[]— one or more election nodes (id,domainId,stateMachine, scripts, optional CoT,electionGate,activeHealthCheck, …)
Bind a bridge to an NSM resource with nsmResource in bridges.json (see below). Prefer nsmResource.domainId + nsmResource.id when multiple NSM nodes exist. Leave nsm empty/disabled if you are not doing HA election.
Optional RTI Cloud enrollment (enabled, enrollmentCode, serviceBaseUrlPrefix). Leave disabled unless onboarding this EBS instance to RTI Cloud.
Baseline policy for the embedded Engage Engine. Full Engine policy is large; EBS cares most about the following.
-
entitlement,key,activationCode,deviceId,manufacturerId - EBS will not run without a valid license key for your deployment model.
-
deviceIdcan be obtained withengagebridged -getdeviceid(see Licensing).
Signed feature blob (signature, lockToDeviceId, features). Payload-processing and some NSM-related capabilities are gated by features—coordinate with RTS / your OEM packaging.
-
certificate/key— Engine identity (PEM or@certstore://) -
caCertificates— CAs for verifying peers (Rallypoints, etc.)
-
defaultNic— default NIC for the Engine -
multicastRejoinSecs,requireMulticast,preventMulticastFailover,addressResolutionPolicy -
rallypointRtTestIntervalMs— RP RTT probe interval from the Engine -
rpUdpStreaming— client-side UDP media toward Rallypoints (enabled,port, keepalive, priority, TTL). See Rallypoint Link Options. -
rtpProfile— jitter buffer profile for RTP paths -
logRtpJitterBufferStats— diagnostic logging
-
maxLevel,enableSyslog— Engine log verbosity / syslog
The shipped conf also includes Engine passthrough trees such as audio, discovery, timelines, database, namedAudioDevices, externalCodecs, rtpMap, nested statusReport, and nested internals / apiCallPacing. For most bridge deployments leave these at defaults. rtpMap can matter when fixing RTP payload-type mismatches—see Dealing With The RTP Payload Type. Discovery and timelines behave as on other Engage hosts; see Engage Advertising And Discovery and Engage Timelines if you enable them.
Activation-code licensing is tied to the device EBS runs on. Print the Engine-generated device ID:
$ engagebridged -getdeviceid
B87CD0E8C3130041A9107AB1DC0905C8
$Only that string is printed, so scripts can capture it for activation workflows. Put the resulting values into enginePolicy.licensing (and featureset when required for payload/HA features).
With EBS running, supply JSON that describes groups and bridges. That topology changes often, so it is not embedded in the service conf. It comes from bridgingConfigurationFileName or bridgingConfigurationFileCommand, polled on bridgingConfigurationFileCheckSecs (or woken via configurationCheckSignalName). Reloads replace the full bridging set.
Packaged default path is typically /etc/engagebridged/bridges.json. Top-level shape:
{
"bridges": [ /* Bridge objects */ ],
"groups": [ /* Group objects */ ]
}-
id— unique bridge ID -
name— human-readable name -
groups— array of group IDs that participate in this bridge -
enabled— when false, EBS keeps the definition but does not run the bridge (ebs-cfg “Toggle Bridge Enabled”) -
active— optional operational active flag where used by tooling/HA -
nsmResource— optional{ "id": "<resourceId>", "domainId": "<nsm-domain>" }binding this bridge to an embedded NSM election resource. PreferdomainIdover legacynsmNodeId. Deprecated bareresourcestrings may still load with warnings.
Minimal fields depend on mode, but typically:
-
id,name,type—1= RTP audio,3= raw (other Engine group types as applicable) -
cryptoPassword— group COMSEC material when encrypted -
rx/tx— multicast (or unicast) addressing -
interfaceName— NIC for that group’s traffic -
txOptions—priority/ttlfor TX (group-level; not a top-level EBS service key) -
txAudio— encoder/framing for payload modes (encoder,framingMs, …) -
rallypoints— optional RP cluster for unicast attachment -
bridgeTargetOutputDetail— whenmodeis3(or you need per-target behavior):mode0raw,1multistream,2mixed,3none, plus mix parameters when mixed
EBS must decrypt source-group traffic and re-encrypt for the target when crypto differs. Provide correct crypto (or clear) on each group.
Example narrative continues below with two bridges and four groups.
To keep things simple for our example we're going to assume we have 4 Engage groups (Group 1, Group 2, Group 3, and Group 4) that we need bridged. We're going to put Group 1 and Group 2 in a bridge of their own. Group 3 and Group 4 will be in another bridge. We'll call these bridges, respectively, Bridge A and Bridge B.
Graphically depicted, we want something like this:
Group 1 ----------------------
|
+----------+
| Bridge A |
+----------+
|
Group 2 ----------------------
Group 3 ----------------------
|
+----------+
| Bridge B |
+----------+
|
Group 4 ----------------------
This way, when Bridge A is active, people on Group 1 and Group 2 can talk to each other. The same applies for Group 3 and Group 4 when Bridge B is active.
Now, there's some information the underlying Engage Engine needs to make this happen.
First, it needs to know how each of these groups is configured so that it can attach to the network infrastructure to access the packets produced by the groups. It also needs to know other information such as the encryption information for each group where applicable.
Engage needs to know the encryption info because its highly unlikely that different groups will have the same encryption and, therefore, if packets from
Group 1are sent on toGroup 2usingGroup 1's encryption; entities onGroup 2will not be able to process them. Basically, when sending packets from one group to another, Engage needs to decrypt from the source and re-encrypt for the target. You get the idea.
In addition to having information about the groups participating in the bridges, what's also needed is the makeup of the bridges themselves - obviously. This is actually quite straightforward. All that's needed is for a bridge configuration to list of group IDs to be placed in that bridge.
Here's what our bridge configuration looks like (and we're keeping it simple for now by not including Rallypoint information on a group-by-group basis):
{
"bridges":[
{
"id":"{316b92fa-d928-410b-ba25-2de5dec3f32d}", // ID of the bridge
"name":"Bridge A", // It's name
"groups":
[
"{ac197b82-0f45-86e8-bf53-02ceea2e977c}", // The ID for Group 1 (see below)
"{f38a3c6f-201c-648e-4dbe-1e8565afe0c5}" // The ID for Group 2 (see below)
]
},
{
"id":"{7ecd2f58-790e-435f-b487-ee4e7588fa26}", // ID of the bridge
"name":"Bridge B", // It's name
"groups":
[
"{f38a3c6f-201c-648e-4dbe-1e8565afe0c5}", // The ID for Group 3 (see below)
"{d54e51d0-1cc1-e130-f955-35b8f763cf9b}" // The ID for Group 4 (see below)
]
}
],
"groups":
[
// A group definition. Notice we only need the most basic information for the group
// configuration - being ID, type (3 is raw; use type 1 for RTP audio in payload modes), crypto password, and
// networking information. Also, see how we have txOptions defined for Group 1. This tells EBS
// to set an IP Time To Live value of 42 on the packets it transmits for Group 1. Also, it tells
// EBS to assign a transmission priority of 4 ("voice") to those transmitted packets.
//
// txOptions will override the defaults that EBS uses - which is a priority of 4 and a TTL of 1.
{
"id": "{ac197b82-0f45-86e8-bf53-02ceea2e977c}",
"name": "Group 1",
"type": 3,
"cryptoPassword": "DA8E1B5DFE107CC8544984D780B320355CA62D583D62A8AB9E0D9E02BB74C0B2",
"rx":
{
"address": "239.148.27.191",
"port": 13334
},
"tx":
{
"address": "239.148.27.191",
"port": 13334
},
"txOptions":{
"priority":4,
"ttl":42
}
},
// Another group definition
{
"id": "{f38a3c6f-201c-648e-4dbe-1e8565afe0c5}",
"name": "Group 2",
"type": 3,
"cryptoPassword": "AADDA91BC10F5F304B2631233F99245C742B2F04396D1D4F68B507AC0A215724",
"rx":
{
"address": "239.16.68.135",
"port": 13336
},
"tx":
{
"address": "239.16.68.135",
"port": 13336
}
},
// And another
{
"id": "{d54e51d0-1cc1-e130-f955-35b8f763cf9b}",
"name": "Group 3",
"type": 3,
"cryptoPassword": "ABF706C812EED032F47E56E55DCB0A5BDC7E8CC3A984CCA7E8708D3E6915C76F",
"rx":
{
"address": "239.189.61.254",
"port": 13338
},
"tx":
{
"address": "239.189.61.254",
"port": 13338
}
},
// And our last one, which, by the way, is going via a Rallypoint
// rather than being directly connected to the network
{
"id": "{eef5ae84-bc51-941b-43e8-fde506415ef3}",
"name": "Group 4",
"type": 3,
"cryptoPassword": "62022266D3E22F85B7E23E66EA6C639C0FBD96C37FEF40DD51A06E2119882F44",
"rallypoints": [
{
"host": {
"address": "myrp.testdomain.com",
"port": 7443
}
}
]
}
]
}Pretty straight forward, no!? All that EBS needs is the most basic information about a group and it'll be in a position to bridge it with other groups.
Notice that we've not included information such as audio CODEC. That's because EBS does not process audio or any other type of traffic content - it processes packets! In fact, EBS uses an Engage feature known as "raw groups" where the content of the packets on a group is not processed by the Engine. Rather, on a raw group, packets are simply passed along with only rules governing encryption and transport for the group being taken into account. This design makes for an extremely fast bridging service with almost zero overhead that scales extremely well even on very low-end hardware.
That said, it is important that you include the
typefield in your JSON configuration and set it to a value of3(which means a "raw" group). Even though EBS only processes raw groups, the way in which those are loaded from JSON require thetypefield to be present.
Also notice that a group's configuration can indicate that it is to be connected via a Rallypoint connection - as shown for "Group 4" in the example. That way you can use EBS to bridge not just between groups, but also between transports.
What's also very important to understand is that the bridging configuration provided to EBS is the entire bridging configuration for the EBS instance. In other words, EBS always wants to "see" what the entire bridging setup looks like, not just changes to a previous configuration. While this imposes quite a bit of work on EBS' side to internally determine changes to what's currently active, it means that systems (such as configuration managers and apps) need not concern themselves with the complexities of change management for bridging.
EBS is pretty smart when it sets up bridges to make sure that traffic flows properly to the groups its needed. Let's say we do something like this where we add Bridge C that bridges Group 2 and Group 3.
Group 1 ----------------------
|
+----------+
| Bridge A |
+----------+
|
Group 2 ----------------------
Group 3 ----------------------
|
+----------+
| Bridge B |
+----------+
|
Group 4 ----------------------
Group 2 ----------------------
|
+----------+
| Bridge C |
+----------+
|
Group 3 ----------------------
What'll happen here is that all traffic will flow everywhere. For example: traffic from Group 1 will go to Group 2 via Bridge A as expected. But, because Group 2 and Group 3 are bridged, the traffic from Group 1 will also flow to Group 3. And, because Group 3 and Group 4 are bridged, the traffic from Group 1 will also flow onward to Group 4. That's cool - no worries there.
But ... what happens if we now, in Bridge D, link Group 4 and Group 1? Like so:
Group 1 ----------------------
|
+----------+
| Bridge A |
+----------+
|
Group 2 ----------------------
Group 3 ----------------------
|
+----------+
| Bridge B |
+----------+
|
Group 4 ----------------------
Group 2 ----------------------
|
+----------+
| Bridge C |
+----------+
|
Group 3 ----------------------
Group 4 ----------------------
|
+----------+
| Bridge D |
+----------+
|
Group 1 ----------------------
This will create a loop because the traffic from Group 1 will come back to itself via its link in Bridge D with Group 4. And that's bad!
Or, imagine you try to bridge Group 1 with itself in a bridge. That's also bad!
Or, let's say you only have one group in a bridge. There's little point in that frankly.
Or ... you try to place WAAAY too many groups in a bridge. We certainly can't tell you what's right for your environment but need to draw the line somewhere. So we've limited the number of groups in a bridge to 128. Hopefully that's enough for you.
EBS checks for this kind of stuff when it processes a bridging configuration and will flat-out fail the creation of the bridge that will cause the loop. So you're pretty safe. But it doesn't solve the problem of creating these kinds of loops across different instances of EBS. For example: you could have one instance of EBS (let's say in one datacenter) be configured for Bridge A, Bridge B, and Bridge C. Then on another instance of EBS in an entirely different datacenter you have that instance configured for Bridge D. Each EBS instance will, itself, see nothing wrong with its own configuration but, systemwide you're going to have a loop on your hands. That's going to knock your network right out, trash the user experience, and generally create mayhem.
Bottom-line, be very careful when you're bridging using multiple instances of EBS. Bad things can happen very quickly with this kind of stuff if you're not super-careful.
Earlier we covered the status report JSON for watching EBS health. You can also use:
- ebm (Engage Bridge Monitor) — a Python helper for status JSON: pub/misc/ebm
-
ebs-cfg — interactive editor for
bridges.json/ service conf: pub/misc/ebs-cfg
As we learn about use-cases that folks are interested in, we'll share what we're allowed to below.
So here's an interesting one ... a customer came to us and asked for a bridging setup where they'd like to bridge a stream of multicast RTP packets from a Land Mobile Radio (LMR) gateway to an Engage group. But ... the gateway is on a totally different network from the Engage clients. So what's being asked is that we not only bridge between the radio system (via the LMR gateway), but also bridge between networking domains! With EBS (and Engage as its underlying Engine) it's a breeze.
First, here's a rough visualization of the requirement: We have a network named 'a' on the left where the LMR gateway lives and is attached to the radio system. On the right we have another network named 'b' where the Engage clients are. In both of these domains we have IP multicast but we don't have a means for multicast to flow between the domains. (And we very specifically don't want multicast routing between these two domains for various logistical, technical, and security reasons.) Also, even if we could get the underlying network infrastructure to route the multicast, we don't want the LMR gateway's packets to be accessible to the Engage clients (we won't go into details why not—just trust us).
So, what we'll do is have EBS running on a computer (a Linux box in this case) which has two network interfaces. The first interface (n1) goes to the LMR gateway network (network a) while the second interface (n2) goes to the network b side. Then we need to configure EBS with a bridge comprised of two groups—one pointed to the LMR gateway, the other to the Engage clients. The key here is that these groups will be bound to different network interfaces, thereby creating not just a bridge between the two groups but also a bridge between two network domains.
network a network b
+*************************************************************+ +***************************************+
* * * *
* +------------------+ +-------------+ +-------------------------------+ *
* | p25 radio system | <---> | lmr gateway | | ebs | *
* +------------------+ +-------------+ +- n1 ---------------- n2 --+ *
* ^ ^ * * ^ *
* | | * * | *
* v trunk v * * v p25 *
* ------------------------- * * ------------------------- *
* * * ^ ^ ^ *
+**************************************************************+ * | | | *
* v v v *
* c1 c2 c3 *
* *
+****************************************+
Here's what our configuration looks like. First, the minimalist 'core' configuration for EBS:
{
"id": "bridgeserver01",
"bridgingConfigurationFileName": "./bridging.json",
"certStoreFileName": "./all-rts-certs.certstore",
"enginePolicy": {
"licensing": {
"key": "<your license here>",
"entitlement": "<your entitlement here>"
}
}
}Be aware that the core EBS configuration above is super-minimal to keep things simple. There's a lot more stuff that you could or might need to add but for purposes of this example we'll have our configurations be as simple as possible.
Next, here's the bridging configuration from the bridging.json file:
{
"bridges": [
{
"id": "{a0e8996b-11c2-4b1d-b4e1-4944f181f7ee}",
"groups": [
"{fd5a4b9f-c8e9-4c49-baef-9f8f81fb723c}",
"{1778f110-6822-40a0-a1a1-626d87b09c31}"
]
}
],
"groups": [
{
"id": "{fd5a4b9f-c8e9-4c49-baef-9f8f81fb723c}",
"name": "Trunk",
"type": 3,
"interfaceName": "n1",
"rx": {
"address": "239.16.17.18",
"port": 15000
},
"tx": {
"address": "239.16.17.18",
"port": 15000
}
},
{
"id": "{1778f110-6822-40a0-a1a1-626d87b09c31}",
"name": "P25",
"type": 3,
"interfaceName": "n2",
"rx": {
"address": "239.119.138.104",
"port": 52296
},
"tx": {
"address": "239.119.138.104",
"port": 52296
}
}
]
}This bridging configuration is pretty straightforward and follows the same idea from other examples in this Wiki. However, pay attention to the interfaceName field in the group definitions. For the Trunk group, we're telling EBS to use the n1 network interface; while for the P25 group we're telling EBS to use the n2 network interface.
Also, because we've stipulated type 3 for the groups, EBS won't be doing any processing of the packet payloads. Instead, its just going to forward the packets back and forth between the n1 and n2 interfaces for those groups.
NOTE: The
P25group is the group that the Engage clients participate on so you need to make sure that your Engage clients have this group configured with the same multicast address and port as in the bridging configuration forP25. Also, we've opted not to encrypt theP25group in this example for purposes of simplicity. But that's easy to do by adding thecryptoPasswordfield to theP25group.
Finally, here's what the P25 group needs to look like on the Engage client side. Of course, the multicast addresses and ports need to match the info from bridging.json above.
Notice how the group's ID of {1778f110-6822-40a0-a1a1-626d87b09c31} matches the ID of the P25. While that's not necessary if we're only doing multicast, it becomes important if this group will be accessed via Rallypoints. So best to make sure you use the same ID.
The two most important elements to understand, though, is the type and txAudio items. While we specified a type of 3 (raw) in the EBS bridging configuration; we specify a type of 1 (audio) in the Engage client configuration. And, because of the audio group type, the Engage client will need to know how to transmit to that group. So we need a txAudio element which, in this case, specifies G.711ulaw as the encoder (with a value of 1). This is super important because EBS is just going to relay the packets from the Engage clients to the LMR gateway - without any processing such as transcoding. So the Engage clients need to transmit their packets in a format that the LMR gateway understands.
{
.
.
.
{
"id": "{1778f110-6822-40a0-a1a1-626d87b09c31}",
"name": "P25",
"type": 1,
"rx": {
"address": "239.119.138.104",
"port": 52296
},
"tx": {
"address": "239.119.138.104",
"port": 52296
},
"txAudio": {
"encoder": 1,
"framingMs": 20,
"maxTxSecs": 30
}
}
.
.
.
}Now that we've covered the general operation of EBS and how to setup bridges, groups, and so on; it's time to look a little deeper at what's happening to those packets traversing the bridges we've described so far. And, so far, the answer is "nothing much". In fact, the only kind of processing done on packets being forwarded between groups in a bridge is the stripping of encryption from the source packets and re-encryption to the target group(s). And that only applies if they're even encrypted. If the groups are not encrypted, EBS is just forwarding the packet as-is with no modifications applied at all.
Now that's perfectly fine in many situations but there are situations where we want to perform some form of transformation on the stream of traffic in a bridge.
A great real-world example is where we want to bridge between traffic from a two-way radio system and a Engage-powered system on the enterprise network. The example we used earlier of bridging a P25 radio system to the enterprise is a good starting point.
If you'll remember, this is kind of we're looking at.
network a network b
+*************************************************************+ +***************************************+
* * * *
* +------------------+ +-------------+ +-------------------------------+ *
* | p25 radio system | <---> | lmr gateway | | ebs | *
* +------------------+ +-------------+ +- n1 ---------------- n2 --+ *
* ^ ^ * * ^ *
* | | * * | *
* v trunk v * * v p25 *
* ------------------------- * * ------------------------- *
* * * ^ ^ ^ *
+**************************************************************+ * | | | *
* v v v *
* c1 c2 c3 *
* *
+****************************************+
But let's get rid of some of those lines and other goop to make it a bit easier to look at. And, at the same time let's not tie ourselves to a channel named "p25" (which denotes the kinds of devices we're using) but rather to a group named "Rescue". Hence, on the "enterprise" side we'll have an Engage channel/group named Rescue and on the P25 radio system, we'll assume a particular talkgroup—let's say Talkgroup 1—is designated as the Rescue talkgroup.
Finally, instead of having EBS connect on the enterprise side to IP multicast, let's rather have it going to a Rallypoint. Kinda like this:
enterprise network
+***************************************+
* *
+------------------+ +-------------+ +-------------------------------+ *
| p25 radio system | <---> | lmr gateway | | ebs | *
+------------------+ +-------------+ +- n1 ---------------- n2 --+ *
^ ^ * ^ *
| | * | *
v trunk v * | rescue *
------------------------- * v *
* +----------------------+ *
* | RP | *
* +----------------------+ *
* ^ ^ ^ *
* | | | *
* v v v *
* c1 c2 c3 *
* *
+****************************************+
The cool thing about just this simple change in thinking about naming the channel for the purpose it serves - that being to do with rescue operations - is a lot easier to understand that trying to remember something like "P25 Channel 1 is for rescue". Also, because we've removed the term "P25" from the group/channel's name; we disconnect ourselves from the fact that there's even a radio system connected to our enterprise system - or even that it's P25! So, if we decide at some point to change from using P25 radios to MANET radios, DMR, or some other kind of 3rd-party system on the "radio side", it doesn't affect our enterprise network users.
OK, that's it for the concept part. Let's look at that configuration. For purposes of this discussion, let's dispense with those hard-to-remember GUIDs and, rather, use something more readable. Frankly, Engage doesn't care what your group identifiers are - as long as they're unique. So you could use "{ac197b82-0f45-86e8-bf53-02ceea2e977c}", or "gov.state.wa.rescue", or "our-super-unique-rescue-group-id" as your group IDs - just be sure they're unique.
Our bridging configuration for this setup is shown below.
{
"bridges":[
{
"id":"Rescue-Bridge",
"name":"Bridge for Rescue traffic",
"groups":
[
"rescue-trunk",
"rescue"
]
}
],
"groups":
[
{
"id": "rescue-trunk",
"name": "Rescue Trunk To P25",
"type": 1,
"txAudio": {
"encoder":1,
"framingMs":20
},
"rx":
{
"address": "239.148.27.191",
"port": 13334
},
"tx":
{
"address": "239.148.27.191",
"port": 13334
},
"txOptions":{
"priority":4,
"ttl":64
}
},
{
"id": "rescue",
"name": "Rescue",
"type": 1,
"txAudio": {
"encoder":25,
"framingMs":60
},
"cryptoPassword": "62022266D3E22F85B7E23E66EA6C639C0FBD96C37FEF40DD51A06E2119882F44",
"rallypoints": [
{
"host": {
"address": "myrp.testdomain.com",
"port": 7443
}
}
]
}
]
}There's two significant changes here that need to be understood in the group definitions.
-
The "type" field for the groups is now
1(denoting "RTP audio media"), instead of3("raw") as we had before. -
Given that we want EBS to process the groups as RTP media, we need to tell it what CODEC to use when transmitting. In the case of the
rescue-trunk, we'll assume that the LMR gateway is configured to use G.711 µ-law at a framing size of 20 milliseconds. Hence,rescue-trunk's "encoder" is Engage CODEC1—meaning G.711 µ-law—with a "framingMs" value of20. On the enterprise group—rescue—we've decided to go with Opus encoding at 16 kbps with a framing size of 60 ms. In Engage-speak, that is encoder25with a framing milliseconds value of 60.
Check out Engage Encoder Information for a list of CODECs and their Engage identifiers.
Finally, there's a change we need to make to EBS's core configuration—in engagebridged_conf.json—to tell it we want payload processing. For mixed anonymous audio output use mode 2 (see mode for 0–3). Ensure enginePolicy.featureset includes the payload-processing feature your license requires.
{
"id":"bridgeserver01",
"mode":2, <---------- PAYLOAD / MIXED-STREAM MODE
"certStoreFileName":"/etc/engagebridged/engagebridged.certstore",
"certStorePasswordHex":"",
.
.
.
}When we now fire up EBS, it'll connect over IP multicast to the LMR gateway, exchanging unencrypted G.711 µ-law traffic at 20 ms intervals with the gateway and, therefore, the radio system. That traffic will be bridged to the enterprise group with the requisite transcoding between G.711 and Opus, as well as re-framing of the packet sizes.
This business of re-framing is actually quite a big deal. Consider sending 20 ms of audio in an RTP packet… That equates to 50 packets per second. And each packet has a whole bunch of IP-related overhead in the form of packet headers—RTP, UDP, IP, Ethernet, and so on. (Together, they're called the "IP Tax".) Nobody likes paying taxes, so if we change the amount of audio in a packet—say from 20 ms to 40 ms—we're now sending 25 packets per second instead of 50. That slashes our IP tax in half and saves bandwidth. At 60 ms we're at about 16.67 packets per second. Larger packets mean a bit more latency, but in a PTT environment those milliseconds rarely matter to users. Engage will allow you to go up to 120 ms per packet for Opus and still deliver solid performance with a greatly reduced bandwidth cost.
Sweet, huh!? Let's dig a little deeper—mismatched "RTP Payload Types".
We have to get a bit down in the weeds for this, but bear with us. It's not too bad.
The RTP standard specifies a field in the RTP header known as the "Payload Type" (PT)—a 7-bit field a receiver can use to determine (or confirm) what media follows the RTP header. When RTP was young, people assumed every media type would fit in 7 bits and be registered with IANA on their RTP Payload Types page. Reality grew faster: you get a small set of static IANA types (roughly 0–18 with reserved holes) and a dynamic range up through 127 that is a bit of a Wild West. G.711 µ-law has a well-known PT (0); modern codecs like Opus typically use a dynamic PT that endpoints must agree on somehow.
In signalled environments (SIP, H.323, and friends) endpoints negotiate codec and PT during call setup. Unsignalled PTT/multicast paths have no handshake—the receiver must already know what PT 103 (for example) means. In a pure Engage mesh that is fine: Engage picks consistent defaults. Between Engage and a third-party gateway, PT 103 might mean Opus on one side and something else on the other. Disaster if neither side can adapt.
Engage can adapt. Configure the Engine’s RTP map so inbound/outbound PT values match what the far end expects—via enginePolicy.rtpMap in engagebridged_conf.json and/or group-level RTP settings your OEM documents for the gateway. Typical approach:
- Learn what PT the LMR gateway (or other peer) emits and expects for each codec.
- Align Engage’s encoder selection (Engage Encoder Information) with that codec.
- Add
rtpMapentries (or gateway-side maps) so Engage tags TX with the peer’s PT and accepts the peer’s PT on RX. - Restart or reload EBS, then confirm with a packet capture that PT and codec match in both directions.
If audio is one-way or garbled only on the bridged path, PT/codec mismatch is a prime suspect—right after wrong interfaceName, multicast addressing, or crypto.
For building and toggling bridges without hand-editing JSON every time, use the ebs-cfg tool:
https://github.com/rallytac/pub/tree/main/misc/ebs-cfg
Example session (abridged):
=== Engage Bridging Service Configuration v0.1 ===
Copyright (c) 2025 Rally Tactical Systems Inc
Working on: bridges.json
==================================================
Bridges:
# * ID En Groups Info
------------------------------------------------------------------------------------------------------------------------
1 audio Y audio01 RP:demo.rallytac.com:7443 Enc:25 (Opus 16 (kbit/s))
audio01-trunk MC:234.5.6.7:2000 IF:en0 Enc:20 (Opus 6 (kbit/s)) noHdrExt:True
2 tsm N tsm01 RP:demo.rallytac.com:7443 Enc:25 (Opus 16 (kbit/s))
tsm01-trunk MC:234.5.6.7:3000 IF:en0 Enc:1 (G.711 U-Law 64 (kbit/s)) [RTP:0 TSM] noHdrExt:True
Add bridge wizards:
1. Raw Bridge (Multicast to Rallypoint)
2. Audio Bridge (Multicast to Rallypoint)
...
7. Toggle Bridge Enabled (enter number)
8. Edit Bridges & Groups
9. EBS Service Configuration
q. Quit
Select option:
Related reading: Engage LMR Interoperability (gateway-oriented walkthroughs), Engage Rallypoints (when a bridge leg rides unicast), and Using nsm (HA election with EBS).