Add Homer - NormB/sipnab GitHub Wiki
Homer keeps a searchable history of your SIP traffic.
- Your SIP servers send it a copy of every message they handle.
- HEP (the Homer Encapsulation Protocol) wraps each copy.
- Homer stores each one.
- Homer shows you any past call as a ladder of messages.
It answers "what happened to that call yesterday" without anyone having run a capture at the time.
This guide stands up Homer beside an OpenSIPS proxy and has OpenSIPS send it every call. With Kamailio instead, one section replaces the OpenSIPS steps.
This guide does not use sipnab. When you have this working, Connect sipnab to Homer adds sipnab:
- as a second receiver,
- as another source.
The parts, and what each one does:
| Part | Role |
|---|---|
| heplify-server | Receives HEP on UDP port 9060 and writes each message into PostgreSQL. |
| PostgreSQL | Stores the messages, one table per message type and day. |
| homer-app | The web interface and its API, on port 9080. It reads what heplify-server stored. |
| OpenSIPS | Your SIP proxy. Its tracer module copies every message of a call to heplify-server over HEP. |
Everything below runs on one machine, which keeps the example short. The last section says what changes when they are apart.
Tested on
Every block on this page ran as written, in order, on 2026-09-28. The machines were clean x86_64 virtual machines with 2 cores and 3 GB of memory:
- Debian 13 (kernel 6.12.63),
- Ubuntu 24.04.5 (kernel 6.8.0).
On each, it ran:
- With OpenSIPS built from source on a machine with nothing installed.
- With the OpenSIPS 4.0 packages.
- With Kamailio, and beside OpenSIPS on one machine.
- On different machines, with Homer on its own machine.
On Debian 13, causing each fault under When something does not work produced what it describes. The commands pin the components to the versions below.
| Software | Version or commit |
|---|---|
| Docker Engine / Compose | from the Docker apt repository |
| heplify-server | ghcr.io/sipcapture/heplify-server:1.60.9 |
| homer-app | ghcr.io/sipcapture/homer-app:1.5.21 |
| PostgreSQL | postgres:17.11-alpine |
| OpenSIPS, built here | f46ef9337b, master, 4.1.0-dev |
| OpenSIPS, from packages | 4.0.2, installed as the OpenSIPS guide installs it |
| Kamailio | 6.1.4, installed as the Kamailio guide installs it |
| SIPp (for the test call) | the distribution's sip-tester |
The examples use 192.0.2.10 as the machine's address. Replace it with yours
everywhere it appears.
Before you start: your SIP server
Find your case, and follow the steps it names:
- No SIP server yet.
- Follow every step.
- The Build OpenSIPS step builds OpenSIPS for you.
- You may rather run the OpenSIPS 4.0 packages. Then install them with the Install OpenSIPS step of the OpenSIPS guide, and skip the Build OpenSIPS step here.
- OpenSIPS already runs, from the packages or built from source.
- Skip the Build OpenSIPS step.
- The
proto_hep,traceranddialogmodules come with theopensipspackage and with the default build. So there is nothing to add. - The Configure OpenSIPS to send every call to Homer step finds your configuration file and replaces it.
- On a machine whose script you want to keep, add the lines that step marks to your own script instead.
- Kamailio already runs, or you want Kamailio. Follow these, in order:
- the Install Docker and Start Homer steps,
- With Kamailio, in place of the Build OpenSIPS and Configure OpenSIPS to send every call to Homer steps,
- the steps from Place a test call to Operate it.
- Both OpenSIPS and Kamailio on one machine. Set up OpenSIPS as above, then see With Kamailio, which covers both.
Install Docker
Homer runs as containers. Install Docker Engine and the Compose plugin from the Docker repository:
# Run all of these, in order.
sudo apt-get update
sudo apt-get install -y ca-certificates curl git
sudo install -m 0755 -d /etc/apt/keyrings
. /etc/os-release
sudo curl -fsSL "https://download.docker.com/linux/$ID/gpg" -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$ID $VERSION_CODENAME stable" \
| sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker "$USER"
Log out and back in so that your account's new docker group takes effect.
Then check:
docker compose version
Start Homer
The three services below are the ones in the sipcapture project's own homer7-docker recipe. They leave out the Prometheus, Grafana and Loki it also bundles.
Choose a database password and put it in .env, where Compose reads it:
# Run all of these, in order.
sudo mkdir -p /opt/homer && sudo chown "$USER": /opt/homer
cd /opt/homer
echo "DB_PASS=$(openssl rand -hex 16)" > .env
chmod 600 .env
cat > init-user-db.sh <<'EOF'
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE DATABASE homer_config;
EOSQL
EOF
cat > docker-compose.yml <<'EOF'
services:
db:
image: postgres:17.11-alpine
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: ${DB_PASS}
volumes:
- ./init-user-db.sh:/docker-entrypoint-initdb.d/init-user-db.sh:ro
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "psql -h localhost -U root -c '\\l'"]
interval: 2s
timeout: 3s
retries: 30
restart: unless-stopped
heplify-server:
image: ghcr.io/sipcapture/heplify-server:1.60.9
command: ["./heplify-server"]
ports:
- "9060:9060/udp"
environment:
HEPLIFYSERVER_HEPADDR: "0.0.0.0:9060"
HEPLIFYSERVER_DBSHEMA: homer7
HEPLIFYSERVER_DBDRIVER: postgres
HEPLIFYSERVER_DBADDR: "db:5432"
HEPLIFYSERVER_DBUSER: root
HEPLIFYSERVER_DBPASS: ${DB_PASS}
HEPLIFYSERVER_DBDATATABLE: homer_data
HEPLIFYSERVER_DBCONFTABLE: homer_config
HEPLIFYSERVER_DBROTATE: "true"
HEPLIFYSERVER_DBDROPDAYS: "7"
HEPLIFYSERVER_LOGLVL: info
HEPLIFYSERVER_LOGSTD: "true"
depends_on:
db:
condition: service_healthy
restart: unless-stopped
homer-app:
image: ghcr.io/sipcapture/homer-app:1.5.21
environment:
DB_HOST: db
DB_USER: root
DB_PASS: ${DB_PASS}
ports:
- "9080:80"
depends_on:
db:
condition: service_healthy
restart: unless-stopped
volumes:
db-data:
EOF
docker compose up -d
HEPLIFYSERVER_DBDROPDAYS is how long Homer keeps messages. Here,
heplify-server drops each day's tables after 7 days.
The first start creates the tables. That took about 30 seconds on the test machines.
- heplify-server holds back what arrives until then.
- So a search before it has finished finds nothing.
- Wait until it logs that it has finished, and the web interface answers:
# Run all of these, in order.
cd /opt/homer
until docker compose logs heplify-server | grep -q 'end creating tables'; do sleep 2; done
until curl -fs -o /dev/null localhost:9080; do sleep 2; done; echo ready
Open http://192.0.2.10:9080 and log in as admin, password sipcapture.
Change that password under the user settings before anything else can reach
the port.
Build OpenSIPS
Skip this step if OpenSIPS is already installed, from the packages or from source.
OpenSIPS master builds with compiler optimizations turned off. That is right for OpenSIPS's own developers and wrong for a proxy carrying calls.
- Turn them back on before you build.
- The
proto_hepandtracermodules are part of the default build:
# Run all of these, in order.
sudo apt-get install -y --no-install-recommends build-essential bison flex uuid-dev pkg-config libncurses-dev libssl-dev
sudo mkdir -p /usr/local/src/voice && sudo chown "$USER": /usr/local/src/voice
cd /usr/local/src/voice
git clone https://github.com/OpenSIPS/opensips.git
cd opensips
git checkout f46ef9337b
make Makefile.conf
sed -i 's/^DEFS+= -DCC_O0/#DEFS+= -DCC_O0/' Makefile.conf
make -j2 all
sudo make install
/usr/local/sbin/opensips -V | head -2
Leave DBG_MALLOC as it is. With both it and CC_O0 switched off, this commit
of master does not compile:
net/tcp_conn_defs.hcallsget_ticks()without including the header that declares it.- Only
DBG_MALLOC's headers happen to supply it.
Configure OpenSIPS to send every call to Homer
This configuration is a minimal proxy with tracing added. Your own script does much more. The tracing part is:
- the block marked below,
- the
loadmodulelines it needs.
Write the configuration
The packages and the source build keep their configuration and their modules in different places. The first two lines find them:
Cis your configuration file.Mis the directory your install loads modules from. The script'smpathnames it.
# Run all of these, in order.
for f in /etc/opensips/opensips.cfg /usr/local/etc/opensips/opensips.cfg; do sudo test -f "$f" && C=$f && break; done
for d in /usr/lib/*/opensips/modules /usr/local/lib64/opensips/modules; do [ -f "$d/tm.so" ] && M=$d && break; done
echo "configuration: $C modules: $M"
sudo tee "$C" >/dev/null <<'EOF'
# OpenSIPS as a SIP proxy that sends every call to Homer over HEP.
log_level=3
stderror_enabled=no
syslog_enabled=yes
syslog_facility=LOG_LOCAL0
udp_workers=2
open_files_limit=4096
socket=udp:192.0.2.10:5060 # the address your phones and carriers reach
socket=hep_udp:127.0.0.1:6061 # HEP to Homer leaves through this socket
mpath="MODULES/"
loadmodule "proto_udp.so" # built into the core, but still loaded by name
loadmodule "signaling.so"
loadmodule "sl.so"
loadmodule "tm.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "sipmsgops.so"
loadmodule "dialog.so"
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/run/opensips/opensips_fifo")
# Tracing: where HEP goes, and a name for it that trace() uses.
loadmodule "proto_hep.so"
modparam("proto_hep", "hep_id", "[homer] 127.0.0.1:9060; transport=udp; version=3")
modparam("proto_hep", "hep_capture_id", 101)
loadmodule "tracer.so"
modparam("tracer", "trace_id", "[tid]uri=hep:homer")
route {
if (!mf_process_maxfwd_header(10)) {
send_reply(483, "Too Many Hops");
exit;
}
if (has_totag()) {
if (is_method("ACK") && t_check_trans()) {
t_relay();
exit;
}
if (!loose_route()) {
send_reply(404, "Not here");
exit;
}
t_relay();
exit;
}
if (is_method("CANCEL")) {
if (t_check_trans())
t_relay();
exit;
}
t_check_trans();
if (!is_method("INVITE")) {
send_reply(405, "Method Not Allowed");
exit;
}
record_route();
# --- tracing starts here ---
create_dialog();
trace("tid", "d");
# --- tracing ends here ---
# Where the call goes. Here, a test callee on this machine; in your
# stack, lookup("location"), dispatcher or a carrier.
$du = "sip:127.0.0.1:5070";
t_relay();
}
EOF
sudo sed -i "s|^mpath=\"MODULES/\"|mpath=\"$M/\"|" "$C"
sudo grep '^mpath=' "$C"
What the tracing lines do:
socket=hep_udp:...gives OpenSIPS a socket to send HEP from. Without it, OpenSIPS refuses to start:No binding found for protocol proto_hep.hep_idnames a HEP destination,homer.- It is heplify-server on this machine, HEP version 3 over UDP.
- Without
transport=udp, OpenSIPS sends version 3 over TCP. This heplify-server does not listen on TCP.
hep_capture_idis the number Homer shows as the source of these messages. Give each SIP server its own.trace_iddefinestidas "send tohomer".create_dialog()makes OpenSIPS track the call.trace("tid", "d")sends every message of that dialog, both legs, until it ends.
If you built OpenSIPS from source: give it a user and a unit
The packages come with an opensips user and a systemd unit.
- If you installed them, or if your source build already runs as a service, skip to Check the configuration and start OpenSIPS.
- A fresh build has neither. Run OpenSIPS as its own user, under systemd:
# Run all of these, in order.
sudo useradd --system --home-dir /run/opensips --shell /usr/sbin/nologin opensips
sudo chown root:opensips /usr/local/etc/opensips
sudo chmod 750 /usr/local/etc/opensips
sudo tee /etc/systemd/system/opensips.service >/dev/null <<'EOF'
[Unit]
Description=OpenSIPS SIP server
After=network.target docker.service
[Service]
Type=forking
User=opensips
Group=opensips
RuntimeDirectory=opensips
RuntimeDirectoryMode=775
PIDFile=/run/opensips/opensips.pid
ExecStart=/usr/local/sbin/opensips -P /run/opensips/opensips.pid -f /usr/local/etc/opensips/opensips.cfg -m 64 -M 8
Restart=always
TimeoutStopSec=30s
LimitNOFILE=262144
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
Check the configuration and start OpenSIPS
# Run all of these, in order.
for f in /etc/opensips/opensips.cfg /usr/local/etc/opensips/opensips.cfg; do sudo test -f "$f" && C=$f && break; done
sudo chown root:opensips "$C"
sudo chmod 640 "$C"
sudo opensips -C -f "$C"
sudo systemctl enable opensips
sudo systemctl restart opensips
systemctl is-active opensips
opensips -C checks the configuration and prints config file ok before you
start anything.
Place a test call
SIPp plays both ends:
- a callee on this machine,
- a caller that dials through OpenSIPS.
SIPp's built-in caller ignores the Record-Route header OpenSIPS adds. So its
BYE would miss the proxy and draw 404 Not here. The two sed lines make it
honor the route set, the way a real phone does:
# Run all of these, in order.
sudo apt-get install -y sip-tester
mkdir -p ~/sipp && cd ~/sipp
sipp -sd uac > uac_rr.xml
sed -i 's|<recv response="200" rtd="true">|<recv response="200" rtd="true" rrs="true">|' uac_rr.xml
sed -i -E 's#^( *)(ACK|BYE) sip:\[service\]@\[remote_ip\]:\[remote_port\] SIP/2.0#\1\2 [next_url] SIP/2.0\n\1[routes]#' uac_rr.xml
sipp -sn uas -i 127.0.0.1 -p 5070 -m 1 -bg
sipp -sf uac_rr.xml 192.0.2.10:5060 -i 192.0.2.10 -p 5080 -s echo -m 1 -d 2000 -timeout 30s \
-trace_msg -message_file uac.msg
grep -m1 -i '^Call-ID:' uac.msg
At the end, SIPp's statistics screen shows Successful call at 1. The last
line prints the call's Call-ID.
Find the call in Homer
The web interface at http://192.0.2.10:9080 searches what heplify-server
stored. To check from the command line, query the database heplify-server
writes.
- heplify-server writes in batches, at least every 4 seconds. So the first line waits for the last batch.
- Each row is one message.
- OpenSIPS sent most of them twice, as it arrived and as it left:
# Run all of these, in order.
sleep 5
cd ~/sipp
CALL=$(grep -m1 -i '^Call-ID:' uac.msg | awk '{print $2}' | tr -d '\r')
cd /opt/homer
docker compose exec -T db psql -U root -d homer_data -c \
"select create_date, data_header->>'method' as method
from hep_proto_1_call where data_header->>'callid' = '$CALL' order by create_date"
Operate it
Check health and read the logs.
# Run all of these, in order.
cd /opt/homer && docker compose ps
docker compose logs --tail 20 heplify-server
systemctl is-active opensips
heplify-server logs a line of statistics every five minutes. It counts:
- the HEP packets it received,
- any it filtered or failed to store.
Keep more or fewer days. Change HEPLIFYSERVER_DBDROPDAYS in
/opt/homer/docker-compose.yml and recreate heplify-server:
# Run all of these, in order.
cd /opt/homer
sed -i 's/HEPLIFYSERVER_DBDROPDAYS: "7"/HEPLIFYSERVER_DBDROPDAYS: "14"/' docker-compose.yml
docker compose up -d heplify-server
Restart. A restart of heplify-server loses the HEP packets sent while it is down. HEP over UDP is not retried.
# Run all of these, in order.
cd /opt/homer && docker compose restart
sudo systemctl restart opensips
Uninstall. docker compose down -v removes the containers and the
database volume. That volume is every stored message:
# Run all of these, in order.
cd /opt/homer && docker compose down -v
sudo systemctl disable --now opensips
With Kamailio
Kamailio sends HEP with its own siptrace module. It comes with the
kamailio package.
- Set Kamailio up as the Kamailio guide does, through its Configure Kamailio as a proxy step.
- Then follow the Install Docker and Start Homer steps here for Homer.
- In place of the Build OpenSIPS and Configure OpenSIPS to send every call to Homer steps, add tracing to Kamailio's configuration.
- The lines marked
tracingare the ones to add to your own script:
# Run all of these, in order.
sudo tee /etc/kamailio/kamailio.cfg >/dev/null <<'EOF'
#!KAMAILIO
# Kamailio as a SIP proxy that sends every call to Homer over HEP.
debug=2
log_stderror=no
log_facility=LOG_LOCAL0
children=2
listen=udp:192.0.2.10:5060 # the address your phones and carriers reach
loadmodule "tm.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "siputils.so"
loadmodule "textops.so"
loadmodule "pv.so"
loadmodule "kex.so"
loadmodule "corex.so"
loadmodule "ctl.so"
# tracing: HEP version 3 to heplify-server, for every message
loadmodule "siptrace.so"
modparam("siptrace", "duplicate_uri", "sip:127.0.0.1:9060")
modparam("siptrace", "hep_mode_on", 1)
modparam("siptrace", "hep_version", 3)
modparam("siptrace", "hep_capture_id", 101)
modparam("siptrace", "trace_to_database", 0)
modparam("siptrace", "trace_on", 1)
modparam("siptrace", "trace_mode", 1)
request_route {
if (!mf_process_maxfwd_header("10")) {
sl_send_reply("483", "Too Many Hops");
exit;
}
if (has_totag()) {
if (loose_route()) {
t_relay();
exit;
}
if (is_method("ACK") && t_check_trans()) {
t_relay();
}
exit;
}
if (is_method("CANCEL")) {
if (t_check_trans()) {
t_relay();
}
exit;
}
t_check_trans();
if (!is_method("INVITE")) {
sl_send_reply("405", "Method Not Allowed");
exit;
}
record_route();
# Where the call goes. Here, a test callee on this machine; in your
# stack, lookup("location"), dispatcher or a carrier.
$du = "sip:127.0.0.1:5070";
t_relay();
}
EOF
sudo kamailio -c -f /etc/kamailio/kamailio.cfg
sudo systemctl enable kamailio
sudo systemctl restart kamailio
systemctl is-active kamailio
What the tracing lines do:
duplicate_uriis where the copies go: heplify-server on this machine.hep_mode_onandhep_versionwrap each copy in HEP version 3, over UDP.hep_capture_idis the number Homer shows as the source of these messages. Give each SIP server its own.trace_to_database 0sends the copies to Homer only. It sends none to a database of Kamailio's own.trace_on 1andtrace_mode 1copy every message Kamailio receives and sends. The script needs no call tosip_trace().
The steps from Place a test call to Operate it then work unchanged. Use
kamailio in place of opensips in the systemctl commands.
Kamailio, too, sends most messages twice, as each arrived and as it left. The test call's query in the Find the call in Homer step returns 13 rows.
With OpenSIPS on the same machine, Kamailio listens on 5062, as OpenSIPS and Kamailio on one machine sets it up.
- Change
5060to5062in thelistenline above. - Give it its own
hep_capture_id, such as102. Then Homer shows which proxy saw each message. - Both send to the same heplify-server.
Put the parts on different machines
Homer on its own machine. Set Homer up with the Install Docker and Start
Homer steps on that machine, here 192.0.2.20. Point the SIP proxy at it.
- The first line finds the proxy's configuration: the OpenSIPS packages', a source build's, or Kamailio's.
- OpenSIPS also sends HEP from an address that reaches the other machine, rather than from the loopback one:
# Run all of these, in order.
for f in /etc/opensips/opensips.cfg /usr/local/etc/opensips/opensips.cfg /etc/kamailio/kamailio.cfg; do sudo test -f "$f" && C=$f && break; done
sudo sed -i -e 's|\[homer\] 127.0.0.1:9060|[homer] 192.0.2.20:9060|' \
-e 's|^socket=hep_udp:127.0.0.1:6061|socket=hep_udp:192.0.2.10:6061|' \
-e 's|"sip:127.0.0.1:9060"|"sip:192.0.2.20:9060"|' "$C"
sudo grep -nE 'hep_id|hep_udp|duplicate_uri' "$C"
case "$C" in */kamailio/*) sudo systemctl restart kamailio;; *) sudo systemctl restart opensips;; esac
Open UDP 9060 on the Homer machine to your SIP servers only. Open port 9080 to the people who use the interface.
- Place the Place a test call step's test call on the proxy's machine.
- Its last line prints the call's Call-ID.
- Find the call on the Homer machine, with that Call-ID in place of the example's.
- As in the Find the call in Homer step, the first line waits for heplify-server's last batch:
# Run all of these, in order.
sleep 5
[email protected]
cd /opt/homer
docker compose exec -T db psql -U root -d homer_data -c \
"select create_date, data_header->>'method' as method
from hep_proto_1_call where data_header->>'callid' = '$CALL' order by create_date"
Several SIP servers. Give each one the same destination and its own capture id. Then Homer shows which server saw each message. OpenSIPS and Kamailio on one machine is that case, and With Kamailio sets it up.
When something does not work
- OpenSIPS does not start:
No binding found for protocol proto_hep. The configuration loadsproto_hepbut has nosocket=hep_udp:...line. - The search finds nothing.
- Check that heplify-server received anything:
docker compose logs heplify-server. - If it logged no packets, check the
hep_idaddress andtransport=udp.
- Check that heplify-server received anything:
hep_idwithouttransport=udp. OpenSIPS then sent nothing at all to heplify-server, which listens on UDP. Keeptransport=udpinhep_id.- The test call's
BYEgets404 Not here. The caller ignored the route set. Use the editeduac_rr.xml, not SIPp's built-inuac.