README
ΒΆ
SIP-exporter
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
- Quick Start
- Core Technology
- Architecture
- Performance
- Install
- Metrics
- Security
- Development
- Benchmark
- Integration
- License
- Changelog
Key Features
- β‘ 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
/metricsendpoint for scraping - π·οΈ Per-carrier metrics β CIDR-based carrier resolution for all SIP metrics
- π·οΈ Per-device-type metrics β User-Agent classification for all 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)
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_PORT=5060
- SIP_EXPORTER_SIPS_PORT=5061
# 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-5061) 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
Performance
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
Configure
Environment variables:
SIP_EXPORTER_INTERFACE- net interface (required)SIP_EXPORTER_HTTP_PORT- http port for prometheus (default 2112)SIP_EXPORTER_LOGGER_LEVEL- log level (default info)SIP_EXPORTER_SIP_PORT- SIP port (default 5060)SIP_EXPORTER_SIPS_PORT- SIPS port (default 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, seeexamples/carriers.yaml)SIP_EXPORTER_USER_AGENTS_CONFIG- path to user-agents YAML config (optional, seeexamples/user_agents.yaml)SIP_EXPORTER_RTP_CAPTURE- enable RTP media capture and analysis (default true)SIP_EXPORTER_RTP_STREAM_TTL- idle RTP stream expiry, RFC 3550 Β§6.3.5 timeout (default 30s)SIP_EXPORTER_IGNORE_OUTGOING- ignore outgoing packets, count incoming only (default false)
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.
Metrics
All metrics are exposed at /metrics in Prometheus exposition format. All SIP metrics include carrier and ua_type labels for multi-dimensional analysis. The exporter provides:
- Traffic counters β SIP request types (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
- 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) - Extended metrics β ISS, SDC, ORD, LRD
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 labeledcarrier="telecom-alpha" - INVITE from
192.168.11.3β metrics labeledcarrier="telecom-beta" - INVITE from
8.8.8.8(not in any subnet) β metrics labeledcarrier="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/32for 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:
- ./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_typefrom the request tracker, not from the response's own headers - The
User-Agentheader is extracted from all SIP packets, but SIP responses typically use theServerheader, 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
carrierandua_typelabels 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
RTP Media Analysis
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 on by default and can be disabled with SIP_EXPORTER_RTP_CAPTURE=false (the eBPF filter then drops RTP at the kernel level). Note: RTP without a correlated SIP dialog (no SDP exchange seen) is dropped, so only media for monitored calls is counted.
# 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
- 120 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.
Integration
Alerting
Pre-configured alerting examples are available in ALERTING.md:
- Prometheus alert rules β Critical, warning, and info alerts for SER, ISA, RRD, and more
- Grafana dashboard β Ready-to-import JSON with carrier-filtered panels
- Alertmanager examples β Slack, PagerDuty, and Email integrations
- Best practices β Scrape intervals, retention, threshold tuning
Grafana Dashboard
Import the pre-built dashboard into your Grafana instance:
- Open Grafana β Dashboards β Import
- Upload
examples/grafana-dashboard.jsonor copy the JSON content - Select your Prometheus or VictoriaMetrics datasource
The dashboard includes all available metrics: traffic counters, SIP request/response breakdowns, active sessions, RFC 6076 performance metrics (SER, SEER, ISA, SCR, NER), 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), session quality metrics (ISS, ASR, SDC), and system errors.
Dashboard file: examples/grafana-dashboard.json
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
/metricsendpoint 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.
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 CHANGELOG.md for version history.