Claude-skill-registry kamailio-config
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/kamailio-config" ~/.claude/skills/majiayu000-claude-skill-registry-kamailio-config && rm -rf "$T"
manifest:
skills/data/kamailio-config/SKILL.mdsource content
Kamailio Configuration
Quick Reference
Configuration Syntax
#!KAMAILIO # Config marker #!define FLAG_NAME 1 # Preprocessor define loadmodule "module.so" # Load module modparam("module", "param", value) # Set parameter request_route { } # Main routing block route[NAME] { } # Named subroute failure_route[NAME] { } # Failure handler onreply_route[NAME] { } # Reply handler branch_route[NAME] { } # Branch handler
Common Pseudo-Variables
| Variable | Description | R/W |
|---|---|---|
| Request URI | R/W |
| Request URI username | R/W |
| Destination URI | R/W |
| From username | R/W |
| Source IP | R |
| Call-ID | R |
| Reply status code | R |
| Script variable | R/W |
| AVP (stack, transaction) | R/W |
| Extended AVP | R/W |
| Dialog variable | R/W |
| Header value | R |
| Unix timestamp (cached) | R |
| Unix timestamp (non-cached, real-time) | R |
| Current time microseconds (0-999999) | R |
Transformations
$(var{s.len}) # String length $(var{s.int}) # To integer $(var{s.tolower}) # Lowercase $(var{s.substr,0,5}) # Substring $(var{s.select,1,:}) # Split and select $(var{s.escape.param}) # URL encode $(uri{uri.user}) # URI username $(uri{uri.host}) # URI domain
Syntax Validation (Quick Check)
# Default (validates kamailio/kamailio.cfg) .claude/skills/kamailio-config/scripts/check-kamailio.sh # Custom config path .claude/skills/kamailio-config/scripts/check-kamailio.sh path/to/config.cfg
Exit code 0 = valid (PASS), non-zero = syntax error (FAIL).
The script auto-builds Docker image, substitutes env placeholders, and uses
--platform linux/amd64 for Apple Silicon.
Common Gotchas
String concatenation requires +
operator
+You cannot directly concatenate pseudo-variables or literals without
+. The . character is NOT a concatenation operator:
# WRONG - syntax error at column 63 $var(ts) = $(Ts{s.ftime,%Y-%m-%d %H:%M:%S}).$TV(u); # CORRECT - use + for concatenation $var(ts) = $(TS{s.ftime,%Y-%m-%d %H:%M:%S}) + "." + $TV(u);
$Ts vs $TS - cached vs real-time timestamp
$Ts is cached at transaction start and doesn't change. Use $TS for real-time timestamps:
# WRONG - $Ts cached at transaction start, same value throughout $var(event_time) = $(Ts{s.ftime,%Y-%m-%d %H:%M:%S}) + "." + $TV(u); # CORRECT - $TS is non-cached, gives real-time value $var(event_time) = $(TS{s.ftime,%Y-%m-%d %H:%M:%S}) + "." + $TV(u);
Note: strftime doesn't support microseconds, so append
$TV(u) separately.
No continue
or break
statements
continuebreakKamailio scripting does NOT support
continue or break. Use a validity flag pattern instead:
# WRONG - will cause syntax error while(condition) { if(error) { continue; } # NOT SUPPORTED } # CORRECT - use validity flag while(condition) { $var(valid) = 1; if(error) { $var(valid) = 0; } if($var(valid) == 1) { # process valid items } }
sql_query vs sql_pvquery
- query must be a constant string, no PV evaluationsql_query(con, query, res)
- query can contain pseudo-variables that get evaluatedsql_pvquery(con, query, res)
# WRONG - sql_query with dynamic string concatenation sql_query("rw", "INSERT INTO t VALUES ('" + $var(x) + "')", "res"); # ERROR # CORRECT - sql_pvquery with embedded PVs sql_pvquery("rw", "INSERT INTO t VALUES ('$(var(x){s.escape.common})')", "$avp(res)");
sql_pvquery result parameter
The result parameter must be a pseudo-variable, not a plain string:
# WRONG sql_pvquery("rw", "INSERT ...", "ra"); # ERROR: invalid result parameter # CORRECT sql_pvquery("rw", "INSERT ...", "$avp(res)");
Docker base image entrypoint
The
ghcr.io/kamailio/kamailio:6.0.1-noble image has a hardcoded ENTRYPOINT that ignores CMD. Override in Dockerfile:
# Clear base image entrypoint to use our startup script ENTRYPOINT [] CMD ["/usr/local/bin/start-kamailio.sh"]
Variable comparison type conversion errors
Comparing variables with
== $null or == "" can trigger type conversion errors:
automatic string to int conversion for "null" failed
Use
keyword and regex matching instead:defined
# WRONG - may cause type conversion error if($dlg_var(trunk_id) != $null && $dlg_var(trunk_id) != "") { # use trunk_id } # CORRECT - use defined and regex for null-safe comparison if(defined $dlg_var(trunk_id) && $dlg_var(trunk_id) =~ "^[0-9]+$") { # use trunk_id - validated as numeric } # For string "null" from JSON parsing if($var(value) =~ "^null$") { $var(value) = ""; # convert to empty }
$avp vs $dlg_var scope
- Transaction-scoped, cleared after transaction ends. Empty for BYE/re-INVITE.$avp(x)
- Dialog-scoped, persists for entire call duration. Use for call-level data.$dlg_var(x)
# In LCR routing - store trunk_id in both $avp(trunk_id) = $var(selected_trunk); $dlg_var(trunk_id) = $avp(trunk_id); # Persist for entire dialog # In onreply_route/failure_route - use $dlg_var # $avp(trunk_id) may be empty here for BYE messages $var(trunk_id_json) = $dlg_var(trunk_id); # CORRECT
CANCEL doesn't trigger branch_route
CANCEL requests are handled specially by the transaction module. Adding CANCEL to
t_on_branch() method list won't work - t_relay() for CANCEL just forwards to cancel the existing INVITE transaction without creating new branches.
To capture CANCEL requests, handle them directly in
request_route:
# In request_route if (is_method("CANCEL")) { if (t_check_trans()) { # Capture CANCEL event HERE - before route(RELAY) xlog("L_NOTICE", "CANCEL request for callid=$ci\n"); route(RELAY); } exit; }
jansson_get returns "null" string for JSON null
When parsing JSON with
jansson_get, a JSON null value becomes the string "null":
# JSON: {"trunk_id":null} jansson_get("trunk_id", $var(json), "$var(trunk_id)"); # $var(trunk_id) now contains string "null", not $null # Check with regex, not equality if($var(trunk_id) =~ "^null$") { $var(trunk_id) = ""; }
mqueue uses key for deduplication
The mqueue module's
mq_add(queue, key, value) uses the key for deduplication - adding items with the same key overwrites previous entries instead of adding to the queue:
# WRONG - same callid as key, later events overwrite earlier ones mq_add("cdr_events", $ci, $var(json1)); # INVITE request mq_add("cdr_events", $ci, $var(json2)); # 180 response - OVERWRITES! mq_add("cdr_events", $ci, $var(json3)); # 200 response - OVERWRITES! # Only json3 remains in queue # CORRECT - use unique key per event (callid + zero-padded microseconds) $var(usec_padded) = $(TV(u){s.int}) + 1000000; mq_add("cdr_events", $ci + "-" + $(var(usec_padded){s.substr,1,6}), $var(json));
$TV(u) microseconds not zero-padded
$TV(u) returns microseconds 0-999999 but does NOT zero-pad the value. This causes sorting issues when used in timestamps or as unique keys:
# WRONG - 69225 vs 100421 sorts incorrectly as strings $var(event_time) = $(TS{s.ftime,%Y-%m-%d %H:%M:%S}) + "." + $TV(u); # CORRECT - add 1000000 and take last 6 digits for zero-padding $var(usec_padded) = $(TV(u){s.int}) + 1000000; $var(event_time) = $(TS{s.ftime,%Y-%m-%d %H:%M:%S}) + "." + $(var(usec_padded){s.substr,1,6}); # Result: 069225 sorts correctly before 100421
Reference Files
Core Documentation
- pseudovariables.md - Complete PV reference (all variables)
- transformations.md - All transformation types
- routing.md - SIP routing flow explanation
- syntax-checking.md - Config validation with Docker
- modules-list.md - All available Kamailio modules with links
Module Documentation (Detailed)
- tm.md - Transaction Management
- dialog.md - Dialog tracking
- acc.md - Accounting/CDR
- sqlops.md - SQL operations
- jansson.md - JSON parsing
- http_client.md - HTTP requests
- rr.md - Record-Route
- sl.md - Stateless replies
External Resources
- Module Docs (6.0.x)
- Wiki Cookbooks
- GitHub Source
- Docker Image - Project uses
6.0.1-noble