SIP-exporter
EN | RU
High-performance eBPF-based SIP monitoring service that captures and exports telephony metrics to Prometheus-compatible systems (Prometheus, VictoriaMetrics, etc.).
Captures SIP packets directly in the Linux kernel using eBPF, minimizing userspace processing overhead.

Table of Contents
Key Features
- π Multi-interface monitoring β capture SIP/RTP from multiple NICs simultaneously, each tagged with an
iface label
- β‘ Low overhead β eBPF packet filtering in kernel space
- π³ Single container deployment β no external dependencies
- π§ Configurable SIP ports β monitor custom ports via environment variables
- π Prometheus native β standard
/metrics endpoint for scraping
- π·οΈ Per-carrier metrics β CIDR-based carrier resolution for all SIP metrics
- π·οΈ Per-device-type metrics β User-Agent classification for all SIP metrics
- π Geo-enrichment β
source_country (GeoIP) and destination_country (E.164 prefix) labels on SIP metrics
- π Voice quality (RFC 6035) β MOS scores, jitter, packet loss from SIP PUBLISH/NOTIFY
- π§ RTP media analysis β jitter, packet loss, and MOS (E-model G.107) from RTP streams correlated with SIP dialogs, with no voice payload captured (header-only)
- π‘οΈ Fraud detection β registration scan, INVITE burst, and account-takeover (country change) signals
Quick Start
# docker-compose.yml
services:
sip-exporter:
image: frzq/sip-exporter:latest
privileged: true
network_mode: host
environment:
- SIP_EXPORTER_INTERFACE=eth0
- SIP_EXPORTER_HTTP_PORT=2112
- SIP_EXPORTER_SIP_PORTS=5060
# Optional: carrier labels for per-provider metrics
# - SIP_EXPORTER_CARRIERS_CONFIG=/etc/sip-exporter/carriers.yaml
# Optional: user-agent labels for per-device-type metrics
# - SIP_EXPORTER_USER_AGENTS_CONFIG=/etc/sip-exporter/user_agents.yaml
# volumes:
# - ./examples/carriers.yaml:/etc/sip-exporter/carriers.yaml:ro
# - ./examples/user_agents.yaml:/etc/sip-exporter/user_agents.yaml:ro
docker compose up -d
curl http://localhost:2112/metrics
Access metrics at http://localhost:2112/metrics.
Core Technology
This service uses eBPF (extended Berkeley Packet Filter) attached to AF_PACKET sockets to
intercept SIP packets (UDP/5060) at L4 without overhead of iptables/nftables or userspace daemons like tcpdump.
Filtered packets are delivered to userspace via the socket for efficient Go processing.
Architecture
SIP + RTP Traffic β NIC β eBPF socket filter β AF_PACKET socket β Go poller β SIP parser + RTP tracker β Prometheus
Zero packet loss up to 2,000 CPS (~28,000 PPS) with full SIP dialog lifecycle, at <15% CPU and ~15 MB RAM. GC stop-the-world pauses under 1 ms β 400Γ smaller than socket buffer capacity, ensuring packets are never lost due to GC. Memory is stable under sustained load with no leaks detected.
Go micro-benchmarks:
| Operation |
Latency |
Memory |
| Parse BYE packet (L2βSIP) |
~860 ns |
712 B/op |
| Parse INVITE packet (L2βSIP) |
~1.1 ΞΌs |
808 B/op |
| Parse 200 OK packet (L2βSIP) |
~2.0 ΞΌs |
1176 B/op |
Full load test results: docs/BENCHMARK.md.
Install
docker pull frzq/sip-exporter:latest
Environment variables:
SIP_EXPORTER_INTERFACE - one or more network interfaces, comma-separated (required). Examples: eth0, eth0,eth1,eth2.
SIP_EXPORTER_HTTP_PORT - http port for prometheus (default 2112)
SIP_EXPORTER_LOGGER_LEVEL - log level (default info)
SIP_EXPORTER_SIP_PORTS - one or more SIP ports, comma-separated (default 5060; up to 3 per interface). Use ; for per-interface sets: 5060,5062;5060,5061.
SIP_EXPORTER_OBJECT_FILE_PATH - path to eBPF object file (default /usr/local/bin/sip.o)
SIP_EXPORTER_CARRIERS_CONFIG - path to carriers YAML config (optional, see examples/carriers.yaml)
SIP_EXPORTER_USER_AGENTS_CONFIG - path to user-agents YAML config (optional, see examples/user_agents.yaml)
SIP_EXPORTER_RTP_STREAM_TTL - idle RTP stream expiry, RFC 3550 Β§6.3.5 timeout (default 30s)
SIP_EXPORTER_IGNORE_OUTGOING - loopback/test only: suppress duplicate TX packets on lo (default false, do NOT enable in production)
SIP_EXPORTER_GEOIP_COUNTRY_DB - path to MaxMind GeoLite2-Country.mmdb for source_country label (optional)
SIP_EXPORTER_LOCAL_COUNTRY_CODE - ISO alpha-2 country code for domestic phone-number fallback in destination_country (optional, e.g. RU)
SIP_EXPORTER_HOST_LABELS - enable caller_host/called_host labels on INVITE metrics (default false; opt-in β unbounded cardinality, enable only on trusted/bounded deployments)
SIP_EXPORTER_TELEMETRY - anonymous usage telemetry, opt-out with false (default true)
The container must run with --privileged and --network host (eBPF requires CAP_BPF and access to the network interface). See Security for details on why this is safe.
β οΈ Multi-interface caveat: do not specify interfaces that see the same traffic (bond parent + child, bridge + member, VLAN parent + subinterface, duplicate SPAN ports). Doing so will double-count metrics. When in doubt, list only physical NICs.
Metrics
All metrics are exposed at /metrics in Prometheus exposition format. All SIP metrics include carrier, ua_type, and source_country labels for multi-dimensional analysis. INVITE metrics additionally carry an iface label identifying the network interface that captured the traffic. The exporter provides:
- Traffic counters β SIP request types (INVITE, re-INVITE, BYE, REGISTER, etc.) and response status codes (100β606)
- Active sessions β real-time count of active SIP dialogs
- RFC 6076 performance metrics β SER, SEER, ISA, SCR, ASR, NER, RRD, SPD, TTR, PDD, PBD
- RFC 6035 voice quality metrics β NLR, JDR, BLD, GLD, RTD, ESD, IAJ, MAJ, MOSLQ, MOSCQ, RLQ, RCQ, RERL
- RTP media metrics β
rtp_packets_total, rtp_packets_lost_total, rtp_jitter_milliseconds, rtp_mos_score, rtp_active_streams (labels: carrier,ua_type,codec,source_country)
- Diagnostics β
sip_retransmission_total (SIP Timer A retransmissions), rtp_out_of_order_total (out-of-sequence RTP packets), short_calls_total (calls shorter than 20/60/180 seconds)
Full reference with formulas, examples, and RFC section mapping: docs/METRICS.md
Per-Carrier Metrics
If your SIP infrastructure handles traffic from multiple operators (telecom providers, SIP trunks, PBX clusters), you need to see metrics per operator, not in aggregate.
The carrier feature solves this by mapping IP subnets to operator names. Every metric β INVITE count, SER, active sessions, RRD latency β gets a carrier label, so you can build separate Grafana dashboards and alerts for each operator.
How it works:
The exporter looks at the source IP of every SIP request and matches it against CIDR subnets in a YAML config. When UAC at 10.1.5.20 sends an INVITE, the exporter finds that 10.1.5.20 falls within 10.1.0.0/16 defined for carrier "telecom-alpha", and tags all metrics for this call β the INVITE itself, the 200 OK response, the BYE, even the dialog expiry β with carrier="telecom-alpha".
This means:
- INVITE from
10.1.5.20 β metrics labeled carrier="telecom-alpha"
- INVITE from
192.168.11.3 β metrics labeled carrier="telecom-beta"
- INVITE from
8.8.8.8 (not in any subnet) β metrics labeled carrier="other"
Setup:
# docker-compose.yml
services:
sip-exporter:
image: frzq/sip-exporter:latest
privileged: true
network_mode: host
environment:
- SIP_EXPORTER_INTERFACE=eth0
- SIP_EXPORTER_CARRIERS_CONFIG=/etc/sip-exporter/carriers.yaml
volumes:
- ./examples/carriers.yaml:/etc/sip-exporter/carriers.yaml:ro
# carriers.yaml β map your operators' IP subnets
carriers:
- name: "telecom-alpha"
cidrs:
- "10.1.0.0/16"
- name: "telecom-beta"
cidrs:
- "192.168.10.0/24"
- "192.168.11.0/24"
After that, metrics look like:
sip_exporter_invite_total{carrier="telecom-alpha",ua_type="other"} 1523
sip_exporter_ser{carrier="telecom-alpha",ua_type="other"} 95.2
sip_exporter_ser{carrier="telecom-beta",ua_type="other"} 87.4
sip_exporter_ser{carrier="other",ua_type="other"} 0.0
Things to know:
- Carrier is determined at request time (INVITE/REGISTER/OPTIONS), not response time. If carrier-A sends INVITE and carrier-B answers 200 OK, all metrics still go to carrier-A β the operator who initiated the call
- If source IP doesn't match any CIDR, destination IP is tried. If neither matches β
carrier="other"
- When CIDRs overlap, first match wins β list specific subnets before broad ones
- Without the config file, all metrics get
carrier="other" β nothing breaks
- Each carrier can have multiple CIDRs, and multiple carriers can be defined
- CIDR notation is required β plain IPs without
/ are rejected. Use /32 for a single host, e.g. "10.226.97.5/32" instead of "10.226.97.5"
Full config reference with examples: examples/carriers.yaml
Per-Device-Type Metrics (User-Agent Classification)
If you need to see metrics per SIP device type β IP phones vs softphones vs SBCs β the User-Agent classification feature adds a ua_type label to every metric.
The exporter reads the User-Agent SIP header from each request and matches it against regex patterns in a YAML config. Every metric β INVITE count, SER, active sessions, SPD duration β gets a ua_type label, so you can build separate Grafana dashboards and alerts for each device family.
How it works:
The exporter parses the User-Agent header of every SIP request and matches it against regex patterns in a YAML config. When a phone with User-Agent: Yealink SIP-T46S 66.15.0.10 sends an INVITE, the exporter matches ^Yealink and tags all metrics for this call with ua_type="yealink".
This means:
- INVITE from Yealink phone β metrics labeled
ua_type="yealink"
- INVITE from Grandstream phone β metrics labeled
ua_type="grandstream"
- INVITE with unknown User-Agent β metrics labeled
ua_type="other"
Setup:
# docker-compose.yml
services:
sip-exporter:
image: frzq/sip-exporter:latest
privileged: true
network_mode: host
environment:
- SIP_EXPORTER_INTERFACE=eth0
- SIP_EXPORTER_USER_AGENTS_CONFIG=/etc/sip-exporter/user_agents.yaml
volumes:
- ./examples/user_agents.yaml:/etc/sip-exporter/user_agents.yaml:ro
# user_agents.yaml β map User-Agent patterns to device types
user_agents:
- regex: '(?i)^Yealink'
label: yealink
- regex: '(?i)^Grandstream'
label: grandstream
- regex: '(?i)^Cisco/SPA'
label: cisco_spa
- regex: '(?i)^Kamailio'
label: kamailio
- regex: '(?i)^Asterisk'
label: asterisk
After that, metrics look like:
sip_exporter_invite_total{carrier="telecom-alpha",ua_type="yealink"} 1523
sip_exporter_ser{carrier="telecom-alpha",ua_type="yealink"} 95.2
sip_exporter_ser{carrier="telecom-alpha",ua_type="grandstream"} 87.4
sip_exporter_ser{carrier="telecom-alpha",ua_type="other"} 0.0
Things to know:
- UA type is determined at request time (INVITE/REGISTER/OPTIONS), using the same tracker mechanism as carrier. Responses inherit
ua_type from the request tracker, not from the response's own headers
- The
User-Agent header is extracted from all SIP packets, but SIP responses typically use the Server header, so in practice only requests provide meaningful classification
- If no pattern matches β
ua_type="other"
- When patterns overlap, first match wins β list specific patterns before broad ones
- Without the config file, all metrics get
ua_type="other" β nothing breaks
- Patterns are case-insensitive when using
(?i) prefix
- Works together with carrier β every metric has both
carrier and ua_type labels for two-dimensional analysis
Combined carrier + ua_type queries:
# SER for Yealink phones on a specific carrier
sip_exporter_ser{carrier="telecom-alpha",ua_type="yealink"}
# Active sessions by device type (across all carriers)
sum by (ua_type) (sip_exporter_sessions)
# INVITE rate per carrier per device type
sum by (carrier, ua_type) (rate(sip_exporter_invite_total[5m]))
Full config reference with examples: examples/user_agents.yaml
Geo-Enrichment Labels
The exporter adds geographic context to SIP metrics via two labels:
| Label |
Method |
Scope |
source_country |
GeoIP lookup of source IP (MaxMind GeoLite2-Country) |
All SIP + RTP metrics |
destination_country |
E.164 phone-number prefix (embedded, no DB needed) |
INVITE metrics only |
source_country resolution:
carrier.country β optional field in carriers.yaml, overrides GeoIP (operator-curated)
GeoIP(srcIP) β MaxMind GeoLite2-Country database lookup
"unknown" β fallback when neither is available
destination_country requires no database β the prefix table is embedded in the binary (Google libphonenumber, Apache 2.0). Set SIP_EXPORTER_LOCAL_COUNTRY_CODE for domestic numbers without international prefix.
caller_host / called_host are off by default (SIP_EXPORTER_HOST_LABELS=false). They expose the host part of the SIP From/To URI on invite_total / invite_200_total. Since distinct endpoint identifiers are unbounded, they are opt-in: enable (SIP_EXPORTER_HOST_LABELS=true) only on trusted deployments where the endpoint count is bounded, otherwise they can grow Prometheus memory. See Security > Data Exposed in Prometheus Labels.
Setup:
# docker-compose.yml
services:
sip-exporter:
image: frzq/sip-exporter:latest
privileged: true
network_mode: host
environment:
- SIP_EXPORTER_INTERFACE=eth0
- SIP_EXPORTER_GEOIP_COUNTRY_DB=/data/GeoLite2-Country.mmdb
- SIP_EXPORTER_LOCAL_COUNTRY_CODE=RU # optional: domestic number fallback
volumes:
- ./GeoLite2-Country.mmdb:/data/GeoLite2-Country.mmdb:ro
Full reference with formulas and PromQL examples: docs/METRICS.md > Geo-Enrichment Labels
Step-by-step setup (how to get and connect the MaxMind database): docs/geoip.md
# SER for calls to Russia
sum(rate(sip_exporter_invite_200_total{destination_country="RU"}[5m]))
/ sum(rate(sip_exporter_invite_total{destination_country="RU"}[5m])) * 100
# INVITE rate by destination country
sum by (destination_country) (rate(sip_exporter_invite_total[5m]))
In addition to SIP signaling, the exporter can capture and analyze RTP media streams to measure real call quality (jitter, packet loss, MOS). RTP streams are correlated with SIP dialogs: when a 200 OK to INVITE carries SDP, the exporter registers the negotiated media endpoints and tracks the matching RTP flows until BYE (or Session-Expires expiry). This means RTP metrics inherit the dialog's carrier, ua_type, and the negotiated codec labels.
Metrics produced:
| Metric |
Type |
Description |
sip_exporter_rtp_packets_total |
counter |
RTP packets observed |
sip_exporter_rtp_packets_lost_total |
counter |
packets lost (RFC 3550 sequence-gap accounting) |
sip_exporter_rtp_jitter_milliseconds |
histogram |
interarrival jitter (RFC 3550 A.8) |
sip_exporter_rtp_mos_score |
histogram |
MOS-LQ via ITU-T G.107 E-model (1.0β4.5) |
sip_exporter_rtp_active_streams |
gauge |
active RTP streams correlated with dialogs |
Privacy: only the 12-byte RTP header is captured β voice payload is truncated in the kernel (eBPF) before reaching userspace, so no call audio is inspected or stored.
RTP capture is always enabled. RTP without a correlated SIP dialog (no SDP exchange seen) is dropped, so only media for monitored calls is counted.
The eBPF filter uses SDP-driven RTP detection: media endpoints (IP:port) learned from INVITE 200 OK SDP are inserted into a BPF LRU hash map. Only UDP packets matching a registered endpoint pass the kernel filter β all other UDP is dropped. This eliminates false positives from random UDP traffic on public IPs.
# Average MOS over the last 5m (per codec)
sum by (codec) (rate(sip_exporter_rtp_mos_score_sum[5m]))
/ sum by (codec) (rate(sip_exporter_rtp_mos_score_count[5m]))
# Packet loss ratio by carrier
sum by (carrier) (rate(sip_exporter_rtp_packets_lost_total[5m]))
/ sum by (carrier) (rate(sip_exporter_rtp_packets_total[5m]))
See docs/METRICS.md for the full RTP reference, formulas, and label resolution.
Development
Requirements
- Go 1.25+
- Clang/LLVM (for eBPF compilation)
- Linux kernel with eBPF support
- Root privileges (required for eBPF and packet socket)
Test Coverage
| Package |
Coverage |
internal/config |
100.0% |
pkg/log |
95.5% |
internal/server |
90.5% |
internal/service |
75.4% |
internal/exporter |
64.0% |
Test suite:
- Unit tests β MC/DC standard, all business logic covered
- 158 E2E tests β real SIP traffic via SIPp + testcontainers-go, validates all RFC 6076, RFC 6035, and RTP metrics
- 13 load tests β PPS throughput, VQ reports, concurrent sessions, memory stability, GC pauses, scrape latency
Benchmark
Load testing results: 0% packet loss at 2,000 CPS (28,000 PPS).
See BENCHMARK.md for detailed results, methodology, and optimization notes.
Alerting
Pre-configured Grafana dashboard and Prometheus alert rules are included in the repository.
Grafana dashboard β import manually:
- Grafana β Dashboards β Import
- Upload
examples/grafana-dashboard.json
- Select your Prometheus or VictoriaMetrics datasource
The dashboard includes: traffic counters, SIP request/response breakdowns, active sessions, RFC 6076 performance metrics (SER, SEER, ISA, SCR, NER), registrations (active count, success ratio, failures by code, fraud signals), RTP media analysis (active streams, packet rate, loss rate, MOS, jitter by codec), voice quality metrics (RFC 6035: MOS, jitter, packet loss), delay histograms (RRD, TTR, PDD, SPD, ORD, LRD, PBD), session quality metrics (ISS, ASR, SDC), diagnostics (SIP retransmissions, short calls), and system errors.
Full alerting guide with Prometheus rules, Alertmanager configs (Slack/PagerDuty/Email), and threshold tuning: docs/ALERTING.md
Metrics Storage Compatibility
SIP-Exporter exports metrics in Prometheus exposition format, compatible with:
- Prometheus β pull-based monitoring
- VictoriaMetrics β high-performance time-series database
- Grafana Cloud β cloud-based observability
- Any Prometheus-compatible scraper β the
/metrics endpoint follows the standard format
License
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
See LICENSE for full text.
Third-Party Data Licenses
- MaxMind GeoLite2 (
source_country) β free for internal use with attribution; redistribution/bundling requires a Commercial License. Users download the database separately.
- Google libphonenumber (
destination_country) β Apache License 2.0. E.164 prefix data embedded in the binary at compile time.
Commercial Use
- β
Free for personal and educational use
- β
Free for commercial use with conditions
- β οΈ If you modify and run as a public service, you must open-source your modifications
- π§ For commercial licensing without AGPL requirements, contact the author
Changelog
See the GitHub Releases for version history.