Database structure: Difference between revisions
(Add detailed cdr_rtp section with video stream payload >= 10000 information and SQL examples) |
(VS-1789: Document nth_occurrence column in cdr_custom_headers table) |
||
| (16 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
{{DISPLAYTITLE:CDR Database Table Schema}} | {{DISPLAYTITLE:CDR Database Table Schema}} | ||
''' | '''Database schema reference for VoIPmonitor. The <code>cdr</code> table stores one record per call; related tables store RTP statistics, custom headers, and metadata.''' | ||
= Schema Overview = | |||
= | <syntaxhighlight lang="sql"> | ||
-- List all CDR-related tables | |||
SHOW TABLES LIKE 'cdr%'; | |||
</syntaxhighlight> | |||
<kroki lang="plantuml"> | <kroki lang="plantuml"> | ||
| Line 18: | Line 19: | ||
* ID : bigint <<PK>> | * ID : bigint <<PK>> | ||
-- | -- | ||
id_sensor | id_sensor, calldate, callend | ||
caller, called, duration | |||
sipcallerip, sipcalledip | |||
caller | a_lost, b_lost, a_mos_f1_mult10 | ||
lastSIPresponse_id <<FK>> | |||
sipcallerip | a_ua_id, b_ua_id <<FK>> | ||
b_ua_id | |||
} | } | ||
entity "cdr_next" as cdr_next { | entity "cdr_next" as cdr_next { | ||
* cdr_ID | * cdr_ID <<PK,FK>> | ||
-- | -- | ||
fbasename | fbasename, match_header | ||
} | } | ||
entity "cdr_rtp" as cdr_rtp { | entity "cdr_rtp" as cdr_rtp { | ||
cdr_ID <<FK>> | |||
-- | -- | ||
saddr, daddr, ssrc | |||
saddr | received, lost, payload | ||
received | |||
} | } | ||
entity " | entity "cdr_proxy" as cdr_proxy { | ||
cdr_ID <<FK>> | |||
-- | -- | ||
dst (proxy IP) | |||
} | } | ||
entity " | entity "cdr_sdp" as cdr_sdp { | ||
cdr_id <<FK>> | |||
-- | -- | ||
ip, port, is_caller | |||
} | } | ||
entity " | entity "cdr_siphistory" as cdr_siphistory { | ||
cdr_ID <<FK>> | |||
-- | -- | ||
method, calldate, cseq | |||
} | } | ||
entity "cdr_ua" as cdr_ua { | entity "cdr_ua" as cdr_ua { | ||
* ID | * ID <<PK>> | ||
-- | |||
ua (User-Agent string) | |||
} | |||
entity "cdr_sip_response" as cdr_sip_response { | |||
* ID <<PK>> | |||
-- | -- | ||
lastSIPresponse | |||
} | } | ||
cdr ||--|| cdr_next : "1:1" | cdr ||--|| cdr_next : "1:1" | ||
cdr ||--o{ cdr_rtp : "1:N" | cdr ||--o{ cdr_rtp : "1:N" | ||
cdr ||--o{ cdr_proxy : "1:N" | cdr ||--o{ cdr_proxy : "1:N" | ||
cdr ||--o{ cdr_sdp : "1:N" | |||
cdr ||--o{ cdr_siphistory : "1:N" | |||
cdr }o--|| cdr_ua : "N:1" | |||
cdr }o--|| cdr_sip_response : "N:1" | cdr }o--|| cdr_sip_response : "N:1" | ||
@enduml | @enduml | ||
</kroki> | </kroki> | ||
== | == Column Naming Conventions == | ||
{| class="wikitable" | {| class="wikitable" | ||
| Line 102: | Line 86: | ||
! Pattern !! Meaning !! Example | ! Pattern !! Meaning !! Example | ||
|- | |- | ||
| <code>a_</code> | | <code>a_</code> || Caller's stream (A-leg) || <code>a_lost</code>, <code>a_mos_f1_mult10</code> | ||
|- | |- | ||
| <code>b_</code> | | <code>b_</code> || Callee's stream (B-leg) || <code>b_lost</code>, <code>b_mos_f1_mult10</code> | ||
|- | |- | ||
| <code>_mult10</code> || Divide | | <code>_mult10</code> || Divide by 10 || <code>jitter_mult10 = 15</code> → jitter = 1.5 | ||
|- | |- | ||
| <code>_mult100</code> || Divide | | <code>_mult100</code> || Divide by 100 || Percentage values | ||
|- | |- | ||
| <code>_mult1000</code> || Divide | | <code>_mult1000</code> || Divide by 1000 || <code>packet_loss_perc_mult1000</code> | ||
|} | |} | ||
= The <code>cdr</code> Table = | |||
== | == Call Information & Timing == | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
! Column | ! Column !! Description | ||
|- | |||
| <code>ID</code> || Primary key | |||
|- | |- | ||
| <code> | | <code>id_sensor</code> || Sensor ID (from <code>voipmonitor.conf</code>) | ||
|- | |- | ||
| <code> | | <code>calldate</code> / <code>callend</code> || Start/end timestamps | ||
|- | |- | ||
| <code> | | <code>duration</code> || Total length in seconds | ||
|- | |- | ||
| <code> | | <code>connect_duration</code> || Connected time (after 200 OK) | ||
|- | |- | ||
| <code> | | <code>progress_time</code> || Time to first provisional response ([[Glossary#Post_Dial_Delay_.28PDD.29|PDD]]) | ||
|- | |- | ||
| <code> | | <code>bye</code> || <code>1</code> = normal termination with BYE | ||
|- | |- | ||
| <code> | | <code>whohanged</code> || Which party sent BYE (caller/callee) | ||
|- | |||
| <code>lastSIPresponse_id</code> || FK to <code>cdr_sip_response</code> | |||
|} | |} | ||
== | == Participant Information == | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
! Column | ! Column !! Description | ||
|- | |- | ||
| <code> | | <code>caller</code> / <code>called</code> || Numbers from From/To headers | ||
|- | |- | ||
| <code> | | <code>caller_domain</code> / <code>called_domain</code> || Domain parts | ||
|- | |- | ||
| <code> | | <code>sipcallerip</code> / <code>sipcalledip</code> || IPs stored as integers (use <code>INET_NTOA()</code>) | ||
|- | |- | ||
| <code> | | <code>a_ua_id</code> / <code>b_ua_id</code> || FK to <code>cdr_ua</code> (User-Agent) | ||
|} | |} | ||
== | == Quality Metrics == | ||
=== Packet Loss === | |||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
! Column | ! Column !! Description | ||
|- | |- | ||
| <code> | | <code>a_lost</code> / <code>b_lost</code> || Total RTP packets lost | ||
|- | |- | ||
| <code> | | <code>packet_loss_perc_mult1000</code> || Max loss percentage × 1000 | ||
|- | |- | ||
| <code> | | <code>a_sl1</code> - <code>a_sl10</code> || Distribution of consecutive loss events | ||
|} | |} | ||
=== Jitter & Delay === | |||
=== | |||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
! Column !! Description | ! Column !! Description | ||
|- | |- | ||
| <code> | | <code>a_avgjitter_mult10</code> || Average jitter × 10 | ||
|- | |- | ||
| <code> | | <code>a_maxjitter</code> || Maximum jitter observed | ||
|- | |- | ||
| <code> | | <code>a_d50</code> - <code>a_d300</code> || PDV distribution buckets (0-50ms, 50-70ms, etc.) | ||
|} | |} | ||
=== | === MOS (Mean Opinion Score) === | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
! Column !! Description | ! Column !! Description | ||
|- | |- | ||
| <code> | | <code>a_mos_f1_mult10</code> || [[Comprehensive_Guide_to_VoIP_Voice_Quality#MOS_Calculation_in_VoIPmonitor|MOS]] with 50ms fixed buffer × 10 | ||
|- | |- | ||
| <code> | | <code>a_mos_f2_mult10</code> || MOS with 200ms fixed buffer × 10 | ||
|- | |- | ||
| <code> | | <code>a_mos_adapt_mult10</code> || MOS with adaptive buffer × 10 | ||
|- | |- | ||
| <code> | | <code>mos_min_mult10</code> || Lowest MOS between A/B streams × 10 | ||
|} | |} | ||
=== | === RTCP-Reported Metrics === | ||
These come from endpoint RTCP reports, not calculated by the sniffer. See [[Call_Detail_Record_-_CDR#Understanding_Sniffer_vs_RTCP_Packet_Loss|Sniffer vs RTCP Packet Loss]]. | |||
{| class="wikitable" | {| class="wikitable" | ||
| Line 232: | Line 186: | ||
! Column !! Description | ! Column !! Description | ||
|- | |- | ||
| <code> | | <code>a_rtcp_loss</code> || Packets lost (reported by endpoint) | ||
|- | |- | ||
| <code> | | <code>a_rtcp_avgfr_mult10</code> || Average fraction loss × 10 | ||
|- | |- | ||
| <code> | | <code>a_rtcp_avgjitter_mult10</code> || Average jitter × 10 | ||
|- | |- | ||
| <code> | | <code>a_rtcp_avgrtd_mult10</code> || '''Roundtrip delay (latency)''' in ms × 10 | ||
|} | |} | ||
= | = Related Tables = | ||
== <code>cdr_next</code> (1:1 with cdr) == | |||
Extension table linked via <code>cdr_ID</code>. | |||
{| class="wikitable" | {| class="wikitable" | ||
| Line 249: | Line 205: | ||
! Column !! Description | ! Column !! Description | ||
|- | |- | ||
| <code> | | <code>fbasename</code> || Derived from SIP Call-ID; used to link PCAP files. Special characters replaced with underscores (<code>convertchar</code> in config) | ||
|- | |- | ||
| <code> | | <code>match_header</code> || Content of custom header for linking call legs (<code>matchheader</code> in config) | ||
| | |||
| <code> | |||
|} | |} | ||
== | == <code>cdr_rtp</code> (Per-Stream RTP Stats) == | ||
One row per RTP stream. Multiple streams per call. | |||
{| class="wikitable" | {| class="wikitable" | ||
| Line 262: | Line 218: | ||
! Column !! Description | ! Column !! Description | ||
|- | |- | ||
| <code> | | <code>cdr_ID</code> || FK to <code>cdr</code> | ||
|- | |- | ||
| <code> | | <code>ssrc</code> || RTP Synchronization Source identifier | ||
| | |- | ||
| <code>saddr</code> / <code>daddr</code> || Source/destination IP (as integer) | |||
|- | |- | ||
| <code>sport</code> / <code>dport</code> || Source/destination port | |||
|- | |- | ||
| <code> | | <code>payload</code> || Codec type. '''Video streams: payload ≥ 10000''' (offset applied) | ||
|- | |- | ||
| <code> | | <code>received</code> / <code>lost</code> || Packet counts | ||
|} | |} | ||
{{Tip|To query video streams only: <code>WHERE payload >= 10000</code>}} | |||
{{ | |||
}} | |||
== <code>cdr_sdp</code> (SDP-Negotiated Ports) == | |||
Stores media endpoints declared in SDP. Requires <code>save_sdp_ipport = yes</code>. | |||
{| class="wikitable" | {| class="wikitable" | ||
| Line 303: | Line 241: | ||
! Column !! Description | ! Column !! Description | ||
|- | |- | ||
| <code> | | <code>ip</code> / <code>port</code> || Media IP and port from SDP | ||
|- | |- | ||
| <code> | | <code>is_caller</code> || 1 = caller side, 0 = callee side | ||
|} | |} | ||
{{ | {{Note|<code>cdr_sdp</code> stores what endpoints '''agreed to use''' in SDP; <code>cdr_rtp</code> stores what was '''actually sent'''.}} | ||
== <code>cdr_proxy</code> (Proxy Chain) == | |||
IP addresses of SIP proxies the call traversed. | |||
== | == <code>cdr_siphistory</code> (SIP Method History) == | ||
Stores SIP requests/responses including REFER, BYE, CANCEL, OPTIONS, etc. Requires <code>save_sip_history = all</code>. | |||
< | == <code>cdr_ua</code> / <code>cdr_sip_response</code> (Lookup Tables) == | ||
</ | |||
Lookup tables for User-Agent strings and SIP response texts. | |||
= | = Common SQL Queries = | ||
== Basic CDR Query with IP Conversion == | |||
<syntaxhighlight lang="sql"> | <syntaxhighlight lang="sql"> | ||
SELECT | SELECT | ||
ID, caller, called, | |||
INET_NTOA(sipcallerip) AS caller_ip, | |||
INET_NTOA(sipcalledip) AS called_ip, | |||
duration | |||
FROM cdr | FROM cdr | ||
WHERE calldate >= '2024-01-01' | |||
AND sipcallerip = INET_ATON('192.168.1.50') | |||
LIMIT 10; | LIMIT 10; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== | == Get PCAP Filename (fbasename) == | ||
<syntaxhighlight lang="sql"> | <syntaxhighlight lang="sql"> | ||
SELECT c.ID, c.caller, c.called, cn.fbasename | |||
FROM cdr c | |||
JOIN cdr_next cn ON c.ID = cn.cdr_ID | |||
WHERE c.ID = 12345; | |||
</syntaxhighlight> | |||
- | == Get User-Agent Strings == | ||
<syntaxhighlight lang="sql"> | |||
SELECT c.ID, ua_a.ua AS caller_ua, ua_b.ua AS callee_ua | |||
SELECT | |||
FROM cdr c | FROM cdr c | ||
JOIN | LEFT JOIN cdr_ua ua_a ON c.a_ua_id = ua_a.ID | ||
LEFT JOIN cdr_ua ua_b ON c.b_ua_id = ua_b.ID | |||
WHERE c.calldate >= CURDATE() - INTERVAL 1 DAY | |||
LIMIT | LIMIT 10; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Find Calls Through Specific Proxy == | |||
=== | |||
<syntaxhighlight lang="sql"> | <syntaxhighlight lang="sql"> | ||
SELECT c.ID, c.calldate, c.caller, c.called, | |||
SELECT | INET_NTOA(cp.dst) AS proxy_ip | ||
FROM cdr c | |||
JOIN cdr_proxy cp ON c.ID = cp.cdr_ID | |||
WHERE cp.dst = INET_ATON('192.168.1.100') | |||
LIMIT 100; | |||
FROM cdr | |||
JOIN | |||
WHERE | |||
LIMIT | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== Query Video Stream Quality == | |||
<syntaxhighlight lang="sql"> | <syntaxhighlight lang="sql"> | ||
SELECT cdr_ID, payload, | |||
SELECT | INET_NTOA(saddr) AS src_ip, | ||
received, lost, | |||
(lost / (received + lost)) * 100 AS loss_pct | |||
FROM cdr_rtp | FROM cdr_rtp | ||
WHERE cdr_ID = 12345; | WHERE payload >= 10000 -- Video streams only | ||
AND cdr_ID = 12345; | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== | == Find Calls with Specific SIP Method (REFER) == | ||
<syntaxhighlight lang="sql"> | |||
SELECT cdr_ID, callid, calldate | |||
FROM cdr_siphistory | |||
WHERE method = 'REFER' | |||
AND calldate > '2024-01-01' | |||
GROUP BY cdr_ID; | |||
</syntaxhighlight> | |||
= | = Custom Headers = | ||
== CDR-Level Custom Headers == | |||
CDR custom headers are stored in <code>cdr_next_X</code> tables. Use <code>cdr_custom_headers</code> to find the location: | |||
<syntaxhighlight lang="sql"> | |||
-- Step 1: Find table/column for header | |||
=== | SELECT dynamic_table, dynamic_column | ||
FROM cdr_custom_headers | |||
WHERE header_field = 'X-Customer-ID'; | |||
-- Result: dynamic_table=2, dynamic_column=1 → cdr_next_2.custom_header_1 | |||
-- Step 2: Query the data | |||
SELECT c.ID, c.calldate, cn.custom_header_1 AS customer_id | |||
SELECT | |||
FROM cdr c | FROM cdr c | ||
JOIN cdr_next_2 cn ON c.ID = cn.cdr_ID | |||
WHERE cn.custom_header_1 = '12345'; | |||
WHERE | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== <code> | {{Note|The GUI's [[Reports|Reports]] module does not support grouping by custom headers. Use direct SQL queries.}} | ||
=== <code>cdr_custom_headers</code> Table Structure === | |||
The <code>cdr_custom_headers</code> table stores the configuration for custom header extraction: | |||
{| class="wikitable" | {| class="wikitable" | ||
| Line 488: | Line 358: | ||
! Column !! Description | ! Column !! Description | ||
|- | |- | ||
| <code> | | <code>header_field</code> || SIP header name to capture | ||
|- | |- | ||
| <code> | | <code>dynamic_table</code> || Target table number (cdr_next_X) | ||
|- | |- | ||
| <code> | | <code>dynamic_column</code> || Target column number (custom_header_X) | ||
|- | |- | ||
| <code> | | <code>type</code> || Extraction type (first/last/nth) | ||
|- | |- | ||
| <code> | | <code>nth_occurrence</code> || '''(New in 35.x)''' Specific occurrence number to extract when type is "nth" (e.g., 2 for second occurrence) | ||
|} | |} | ||
{{Note|The <code>nth_occurrence</code> column uses packet timestamps for accurate ordering, ensuring correct results even when packet time-based reordering is disabled in the sniffer.}} | |||
== SIP Message Custom Headers == | |||
< | |||
Per-message headers are stored in <code>sip_msg_next_X</code> tables. Use <code>sip_msg_custom_headers</code> to find the location. | |||
</ | |||
= System Table = | |||
The <code>system</code> table stores configuration including <code>manager_key</code> for sensor operations. | |||
< | |||
</ | |||
<syntaxhighlight lang="sql"> | <syntaxhighlight lang="sql"> | ||
SELECT | SELECT * FROM voipmonitor.`system` WHERE type='manager_key'\G | ||
FROM | |||
WHERE | |||
</syntaxhighlight> | </syntaxhighlight> | ||
= | {{Warning|1=If sniffer fails with "failed read rsa key", check that <code>mysqlloadconfig</code> is NOT set to <code>no</code> in <code>voipmonitor.conf</code>.}} | ||
== Synchronizing manager_key Between Databases == | |||
When multiple GUI instances share sensors (e.g., after database migration), mismatched <code>manager_key</code> values cause PCAP download failures. | |||
'''Symptoms:''' "Unable to download PCAP" errors, empty SIP history in one GUI. | |||
'''Fix:''' | |||
<syntaxhighlight lang="sql"> | |||
-- On active database: get the key | |||
SELECT content FROM voipmonitor.`system` WHERE type='manager_key'; | |||
-- On old database: update the key | |||
-- | UPDATE voipmonitor.`system` | ||
SET content = '{"key":"<key_from_active>","iv":"<iv_from_active>"}' | |||
WHERE type = 'manager_key'; | |||
WHERE | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Users must log out and log back in after the update. | |||
= Troubleshooting = | |||
== | == Missing <code>sport</code>/<code>dport</code> in cdr_rtp == | ||
If RTP streams from overlapping calls are incorrectly merged, add the missing columns: | |||
<syntaxhighlight lang="sql"> | <syntaxhighlight lang="sql"> | ||
ALTER TABLE cdr_rtp ADD `sport` smallint unsigned DEFAULT NULL AFTER `saddr`; | |||
ALTER TABLE cdr_rtp ADD `dport` smallint unsigned DEFAULT NULL AFTER `daddr`; | |||
</syntaxhighlight> | </syntaxhighlight> | ||
{{ | {{Warning|1=<code>ALTER TABLE</code> can lock large tables for hours. Run during maintenance windows.}} | ||
}} | |||
To find other recommended schema changes: | |||
<syntaxhighlight lang="bash"> | |||
<syntaxhighlight lang=" | journalctl -u voipmonitor | grep ALTER | ||
- | |||
</syntaxhighlight> | </syntaxhighlight> | ||
= | = See Also = | ||
* [[Call_Detail_Record_-_CDR|CDR View (GUI)]] | |||
* [[Database_troubleshooting|Database Troubleshooting]] | |||
* [[Sniffer_configuration#Database_Configuration|Database Configuration]] | |||
* [[WEB_API|Web API Documentation]] | |||
= AI Summary for RAG = | |||
'''Summary:''' VoIPmonitor database schema reference. Main table <code>cdr</code> stores one record per call with timing, participants, and quality metrics. Column naming: <code>a_</code>=caller, <code>b_</code>=callee, <code>_multNN</code>=divide by NN. Key tables: <code>cdr_next</code> (1:1, contains <code>fbasename</code> for PCAP linking), <code>cdr_rtp</code> (per-stream stats, video streams have payload≥10000), <code>cdr_proxy</code> (proxy chain IPs), <code>cdr_sdp</code> (SDP-negotiated ports), <code>cdr_siphistory</code> (SIP method history, requires <code>save_sip_history=all</code>). Custom headers stored in <code>cdr_next_X</code> tables; query <code>cdr_custom_headers</code> for mappings. System table stores <code>manager_key</code>; mismatch causes PCAP download failures. | |||
'''Summary:''' | |||
'''Keywords:''' database, schema, cdr, mysql, | '''Keywords:''' database, schema, cdr, mysql, cdr_next, fbasename, cdr_rtp, video payload, cdr_proxy, cdr_sdp, cdr_siphistory, INET_NTOA, INET_ATON, sipcallerip, custom headers, cdr_custom_headers, manager_key, packet loss, jitter, mos, rtcp, a_rtcp_avgrtd_mult10, latency | ||
'''Key Questions:''' | '''Key Questions:''' | ||
* What do | * What do a_ and b_ prefixes mean in cdr table columns? | ||
* How do I convert sipcallerip to | * How do I convert sipcallerip to readable IP address? | ||
* How do I get the | * How do I get the PCAP filename (fbasename) for a call? | ||
* How do I query video streams in cdr_rtp (payload >= 10000)? | |||
* How do I query video streams | * How do I find calls that went through a specific proxy? | ||
* How do I | * How do I find calls with REFER or other SIP methods? | ||
* How do I | * How do I query CDR custom headers? | ||
* How do I | * Why does "failed read rsa key" error occur on startup? | ||
* How do I | * How do I sync manager_key between multiple databases? | ||
* Why | * Why can't old GUI download PCAP files after database migration? | ||
Latest revision as of 11:29, 19 January 2026
Database schema reference for VoIPmonitor. The cdr table stores one record per call; related tables store RTP statistics, custom headers, and metadata.
Schema Overview
-- List all CDR-related tables
SHOW TABLES LIKE 'cdr%';
Column Naming Conventions
| Pattern | Meaning | Example |
|---|---|---|
a_ |
Caller's stream (A-leg) | a_lost, a_mos_f1_mult10
|
b_ |
Callee's stream (B-leg) | b_lost, b_mos_f1_mult10
|
_mult10 |
Divide by 10 | jitter_mult10 = 15 → jitter = 1.5
|
_mult100 |
Divide by 100 | Percentage values |
_mult1000 |
Divide by 1000 | packet_loss_perc_mult1000
|
The cdr Table
Call Information & Timing
| Column | Description |
|---|---|
ID |
Primary key |
id_sensor |
Sensor ID (from voipmonitor.conf)
|
calldate / callend |
Start/end timestamps |
duration |
Total length in seconds |
connect_duration |
Connected time (after 200 OK) |
progress_time |
Time to first provisional response (PDD) |
bye |
1 = normal termination with BYE
|
whohanged |
Which party sent BYE (caller/callee) |
lastSIPresponse_id |
FK to cdr_sip_response
|
Participant Information
| Column | Description |
|---|---|
caller / called |
Numbers from From/To headers |
caller_domain / called_domain |
Domain parts |
sipcallerip / sipcalledip |
IPs stored as integers (use INET_NTOA())
|
a_ua_id / b_ua_id |
FK to cdr_ua (User-Agent)
|
Quality Metrics
Packet Loss
| Column | Description |
|---|---|
a_lost / b_lost |
Total RTP packets lost |
packet_loss_perc_mult1000 |
Max loss percentage × 1000 |
a_sl1 - a_sl10 |
Distribution of consecutive loss events |
Jitter & Delay
| Column | Description |
|---|---|
a_avgjitter_mult10 |
Average jitter × 10 |
a_maxjitter |
Maximum jitter observed |
a_d50 - a_d300 |
PDV distribution buckets (0-50ms, 50-70ms, etc.) |
MOS (Mean Opinion Score)
| Column | Description |
|---|---|
a_mos_f1_mult10 |
MOS with 50ms fixed buffer × 10 |
a_mos_f2_mult10 |
MOS with 200ms fixed buffer × 10 |
a_mos_adapt_mult10 |
MOS with adaptive buffer × 10 |
mos_min_mult10 |
Lowest MOS between A/B streams × 10 |
RTCP-Reported Metrics
These come from endpoint RTCP reports, not calculated by the sniffer. See Sniffer vs RTCP Packet Loss.
| Column | Description |
|---|---|
a_rtcp_loss |
Packets lost (reported by endpoint) |
a_rtcp_avgfr_mult10 |
Average fraction loss × 10 |
a_rtcp_avgjitter_mult10 |
Average jitter × 10 |
a_rtcp_avgrtd_mult10 |
Roundtrip delay (latency) in ms × 10 |
Related Tables
cdr_next (1:1 with cdr)
Extension table linked via cdr_ID.
| Column | Description |
|---|---|
fbasename |
Derived from SIP Call-ID; used to link PCAP files. Special characters replaced with underscores (convertchar in config)
|
match_header |
Content of custom header for linking call legs (matchheader in config)
|
cdr_rtp (Per-Stream RTP Stats)
One row per RTP stream. Multiple streams per call.
| Column | Description |
|---|---|
cdr_ID |
FK to cdr
|
ssrc |
RTP Synchronization Source identifier |
saddr / daddr |
Source/destination IP (as integer) |
sport / dport |
Source/destination port |
payload |
Codec type. Video streams: payload ≥ 10000 (offset applied) |
received / lost |
Packet counts |
💡 Tip:
cdr_sdp (SDP-Negotiated Ports)
Stores media endpoints declared in SDP. Requires save_sdp_ipport = yes.
| Column | Description |
|---|---|
ip / port |
Media IP and port from SDP |
is_caller |
1 = caller side, 0 = callee side |
ℹ️ Note: cdr_sdp stores what endpoints agreed to use in SDP; cdr_rtp stores what was actually sent.
cdr_proxy (Proxy Chain)
IP addresses of SIP proxies the call traversed.
cdr_siphistory (SIP Method History)
Stores SIP requests/responses including REFER, BYE, CANCEL, OPTIONS, etc. Requires save_sip_history = all.
cdr_ua / cdr_sip_response (Lookup Tables)
Lookup tables for User-Agent strings and SIP response texts.
Common SQL Queries
Basic CDR Query with IP Conversion
SELECT
ID, caller, called,
INET_NTOA(sipcallerip) AS caller_ip,
INET_NTOA(sipcalledip) AS called_ip,
duration
FROM cdr
WHERE calldate >= '2024-01-01'
AND sipcallerip = INET_ATON('192.168.1.50')
LIMIT 10;
Get PCAP Filename (fbasename)
SELECT c.ID, c.caller, c.called, cn.fbasename
FROM cdr c
JOIN cdr_next cn ON c.ID = cn.cdr_ID
WHERE c.ID = 12345;
Get User-Agent Strings
SELECT c.ID, ua_a.ua AS caller_ua, ua_b.ua AS callee_ua
FROM cdr c
LEFT JOIN cdr_ua ua_a ON c.a_ua_id = ua_a.ID
LEFT JOIN cdr_ua ua_b ON c.b_ua_id = ua_b.ID
WHERE c.calldate >= CURDATE() - INTERVAL 1 DAY
LIMIT 10;
Find Calls Through Specific Proxy
SELECT c.ID, c.calldate, c.caller, c.called,
INET_NTOA(cp.dst) AS proxy_ip
FROM cdr c
JOIN cdr_proxy cp ON c.ID = cp.cdr_ID
WHERE cp.dst = INET_ATON('192.168.1.100')
LIMIT 100;
Query Video Stream Quality
SELECT cdr_ID, payload,
INET_NTOA(saddr) AS src_ip,
received, lost,
(lost / (received + lost)) * 100 AS loss_pct
FROM cdr_rtp
WHERE payload >= 10000 -- Video streams only
AND cdr_ID = 12345;
Find Calls with Specific SIP Method (REFER)
SELECT cdr_ID, callid, calldate
FROM cdr_siphistory
WHERE method = 'REFER'
AND calldate > '2024-01-01'
GROUP BY cdr_ID;
Custom Headers
CDR-Level Custom Headers
CDR custom headers are stored in cdr_next_X tables. Use cdr_custom_headers to find the location:
-- Step 1: Find table/column for header
SELECT dynamic_table, dynamic_column
FROM cdr_custom_headers
WHERE header_field = 'X-Customer-ID';
-- Result: dynamic_table=2, dynamic_column=1 → cdr_next_2.custom_header_1
-- Step 2: Query the data
SELECT c.ID, c.calldate, cn.custom_header_1 AS customer_id
FROM cdr c
JOIN cdr_next_2 cn ON c.ID = cn.cdr_ID
WHERE cn.custom_header_1 = '12345';
ℹ️ Note: The GUI's Reports module does not support grouping by custom headers. Use direct SQL queries.
cdr_custom_headers Table Structure
The cdr_custom_headers table stores the configuration for custom header extraction:
| Column | Description |
|---|---|
header_field |
SIP header name to capture |
dynamic_table |
Target table number (cdr_next_X) |
dynamic_column |
Target column number (custom_header_X) |
type |
Extraction type (first/last/nth) |
nth_occurrence |
(New in 35.x) Specific occurrence number to extract when type is "nth" (e.g., 2 for second occurrence) |
ℹ️ Note: The nth_occurrence column uses packet timestamps for accurate ordering, ensuring correct results even when packet time-based reordering is disabled in the sniffer.
SIP Message Custom Headers
Per-message headers are stored in sip_msg_next_X tables. Use sip_msg_custom_headers to find the location.
System Table
The system table stores configuration including manager_key for sensor operations.
SELECT * FROM voipmonitor.`system` WHERE type='manager_key'\G
⚠️ Warning: If sniffer fails with "failed read rsa key", check that mysqlloadconfig is NOT set to no in voipmonitor.conf.
Synchronizing manager_key Between Databases
When multiple GUI instances share sensors (e.g., after database migration), mismatched manager_key values cause PCAP download failures.
Symptoms: "Unable to download PCAP" errors, empty SIP history in one GUI.
Fix:
-- On active database: get the key
SELECT content FROM voipmonitor.`system` WHERE type='manager_key';
-- On old database: update the key
UPDATE voipmonitor.`system`
SET content = '{"key":"<key_from_active>","iv":"<iv_from_active>"}'
WHERE type = 'manager_key';
Users must log out and log back in after the update.
Troubleshooting
Missing sport/dport in cdr_rtp
If RTP streams from overlapping calls are incorrectly merged, add the missing columns:
ALTER TABLE cdr_rtp ADD `sport` smallint unsigned DEFAULT NULL AFTER `saddr`;
ALTER TABLE cdr_rtp ADD `dport` smallint unsigned DEFAULT NULL AFTER `daddr`;
⚠️ Warning: ALTER TABLE can lock large tables for hours. Run during maintenance windows.
To find other recommended schema changes:
journalctl -u voipmonitor | grep ALTER
See Also
AI Summary for RAG
Summary: VoIPmonitor database schema reference. Main table cdr stores one record per call with timing, participants, and quality metrics. Column naming: a_=caller, b_=callee, _multNN=divide by NN. Key tables: cdr_next (1:1, contains fbasename for PCAP linking), cdr_rtp (per-stream stats, video streams have payload≥10000), cdr_proxy (proxy chain IPs), cdr_sdp (SDP-negotiated ports), cdr_siphistory (SIP method history, requires save_sip_history=all). Custom headers stored in cdr_next_X tables; query cdr_custom_headers for mappings. System table stores manager_key; mismatch causes PCAP download failures.
Keywords: database, schema, cdr, mysql, cdr_next, fbasename, cdr_rtp, video payload, cdr_proxy, cdr_sdp, cdr_siphistory, INET_NTOA, INET_ATON, sipcallerip, custom headers, cdr_custom_headers, manager_key, packet loss, jitter, mos, rtcp, a_rtcp_avgrtd_mult10, latency
Key Questions:
- What do a_ and b_ prefixes mean in cdr table columns?
- How do I convert sipcallerip to readable IP address?
- How do I get the PCAP filename (fbasename) for a call?
- How do I query video streams in cdr_rtp (payload >= 10000)?
- How do I find calls that went through a specific proxy?
- How do I find calls with REFER or other SIP methods?
- How do I query CDR custom headers?
- Why does "failed read rsa key" error occur on startup?
- How do I sync manager_key between multiple databases?
- Why can't old GUI download PCAP files after database migration?