sip-exporter

module
v1.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: AGPL-3.0

README ΒΆ

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.

Go Test Go Vulncheck Container Scan Go Report Card Docker Pulls GitHub Release License Issues

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

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 - 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:

  1. carrier.country β€” optional field in carriers.yaml, overrides GeoIP (operator-curated)
  2. GeoIP(srcIP) β€” MaxMind GeoLite2-Country database lookup
  3. "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]))
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 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:

  1. Grafana β†’ Dashboards β†’ Import
  2. Upload examples/grafana-dashboard.json
  3. 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.

Directories ΒΆ

Path Synopsis
Command sip-exporter captures SIP/RTP traffic via eBPF and exports Prometheus metrics for VoIP call quality monitoring.
Command sip-exporter captures SIP/RTP traffic via eBPF and exports Prometheus metrics for VoIP call quality monitoring.
internal
carriers
Package carriers maps source IP addresses to carrier names and countries via CIDR-based configuration loaded from YAML.
Package carriers maps source IP addresses to carrier names and countries via CIDR-based configuration loaded from YAML.
config
Package config reads environment variables into a typed App struct.
Package config reads environment variables into a typed App struct.
dto
exporter
Package exporter captures SIP/RTP traffic via AF_PACKET sockets with an eBPF filter, parses messages, correlates dialogs, and updates Prometheus metrics.
Package exporter captures SIP/RTP traffic via AF_PACKET sockets with an eBPF filter, parses messages, correlates dialogs, and updates Prometheus metrics.
geoip
Package geoip provides lookups of ISO country codes from IP addresses using a MaxMind GeoLite2-Country database.
Package geoip provides lookups of ISO country codes from IP addresses using a MaxMind GeoLite2-Country database.
mediatracker
Package mediatracker correlates RTP streams with SIP dialogs, computing jitter, loss, and MOS (RFC 3550 / ITU-T G.107) from observed packets.
Package mediatracker correlates RTP streams with SIP dialogs, computing jitter, loss, and MOS (RFC 3550 / ITU-T G.107) from observed packets.
rtp
Package rtp parses RTP (RFC 3550) fixed headers from raw packet bytes.
Package rtp parses RTP (RFC 3550) fixed headers from raw packet bytes.
sdp
Package sdp parses SDP (RFC 4566) media descriptions from SIP message bodies for RTP stream correlation.
Package sdp parses SDP (RFC 4566) media descriptions from SIP message bodies for RTP stream correlation.
server
Package server wires configuration, metrics, and the eBPF exporter into an HTTP server that serves Prometheus metrics and health endpoints.
Package server wires configuration, metrics, and the eBPF exporter into an HTTP server that serves Prometheus metrics and health endpoints.
service
Package service implements the SIP metrics layer (Prometheus counters, gauges, histograms, and ratio collectors) and the active-dialog tracker.
Package service implements the SIP metrics layer (Prometheus counters, gauges, histograms, and ratio collectors) and the active-dialog tracker.
telemetry
Package telemetry sends periodic anonymous beacon pings to a central endpoint for installation tracking.
Package telemetry sends periodic anonymous beacon pings to a central endpoint for installation tracking.
ua
Package ua classifies SIP User-Agent header values into short labels via regex patterns loaded from YAML.
Package ua classifies SIP User-Agent header values into short labels via regex patterns loaded from YAML.
vq
Package vq parses VQ-RTCPXR (RFC 6035) session reports and routes the extracted metrics to a Metricser.
Package vq parses VQ-RTCPXR (RFC 6035) session reports and routes the extracted metrics to a Metricser.
pkg
log
Package log initializes the global zap logger with configurable verbosity.
Package log initializes the global zap logger with configurable verbosity.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL