本笔记针对有有一定OpenSIPS基础的人来看, 否则,建议您查看OpenSIPS2.4.X 中文实战系列,。
在后文中,我们简称Kamailio为km。
架构图 kamailio 1.x版本 kamailio 3.x版本。相比于1.x版本,两个核心模块移动到外部模块。 核心模块 The core includes: memory manager SIP message parser locking system DNS and transport layer management (UDP, TCP, TLS, SCTP) configuration file parser and interpreter stateless forwarding pseudo-variables and transformations engines RPC control interface API timer API The internal libraries include: some components from old Kamailio v1.5.x core database abstraction layers (DB API v1 and v2) management interface (MI) API statistics engine SIP消息处理 请求处理 响应处理。 这里可以看到,是先执行响应路由,再执行失败路由。 ...
目标 kamailio将所收到的SIP消息封装成HEP格式,然后已UDP发送给Hep Server 环境说明 kamailio版本 5.6 Hep server地址 192.168.0.100 kamailio脚本 listen 参考文档 https://www.kamailio.org/docs/modules/5.6.x/modules/siptrace.html https://github.com/sipcapture/homer/discussions/619 https://github.com/sipcapture/homer/wiki/Examples%3A-Kamailio
[[TOC]] route_list route.h定义了几个函数分别用来获取、查找、新增route // src/core/route.h int route_get(struct route_list *rt, char *name); int route_lookup(struct route_list *rt, char *name); void push(struct action *a, struct action **head); struct route_list { struct action **rlist; int idx; /* first empty entry */ int entries; /* total number of entries */ struct str_hash_table names; /* name to route index mappings */ }; rlist 我们对route_list数据模型进行简化: rlist是一个固定长度的一维数组,通过索引来访问对应的值。如果数组的空间不足,那么就创建一个两倍大的空数据,然后先把原始数据复制过去。这种复制方式保持的原始数据的索引位置。有点像golang的切片扩容机制。 这里最为重要的就是保持数组元素的索引位置在扩容后不变。 static inline int route_new_list(struct route_list *rt) { int ret; struct action **tmp; ret = -1; if(rt->idx >= rt->entries) { // 两倍扩容 tmp = pkg_realloc(rt->rlist, 2 * rt->entries * sizeof(struct action *)); if(tmp == 0) { LM_CRIT("out of memory\n"); goto end; } /* init the newly allocated memory chunk */ memset(&tmp[rt->entries], 0, rt->entries * sizeof(struct action *)); rt->rlist = tmp; rt->entries *= 2; } if(rt->idx < rt->entries) { ret = rt->idx; rt->idx++; } end: return ret; } str_hash_table 我们对hash_table的数据模型进行简化,它其实就是一hash表,key是路由的名,值是一个正数,正数代表了路由执行单元的索引位置。 ...
[[TOC]] 事件路由简介 在某些模块中,我们看到有一些模块自定义的事件路由。 例如dispatcher模块,或者rtpengine模块。 event_route[dispatcher:dst-down] { xlog("L_ERR", "Destination down: $rm $ru ($du)\n"); } event_route[rtpengine:dtmf-event] { xlog("L_INFO", "callid: $avp(dtmf_event_callid)\n"); xlog("L_INFO", "source_tag: $avp(dtmf_event_source_tag)\n"); xlog("L_INFO", "timestamp: $avp(dtmf_event_timestamp)\n"); xlog("L_INFO", "dtmf: $avp(dtmf_event)\n"); } disapcher模块 在dispatch.c文件中,我们看到如下代码 if(!ds_skip_dst(old_state) && ds_skip_dst(idx->dlist[i].flags)) { ds_run_route(msg, address, "dispatcher:dst-down", rctx); } else { if(ds_skip_dst(old_state) && !ds_skip_dst(idx->dlist[i].flags)) ds_run_route(msg, address, "dispatcher:dst-up", rctx); } ds_run_route还是定义在dispatch.c文件中, static void ds_run_route(sip_msg_t *msg, str *uri, char *route, ds_rctx_t *rctx) 接着又一个重要调用。 这里似乎在查找路由。 route这个参数其实就是dispatcher:dst-down, 或者 dispatcher:dst-up, 那么event_rt又是什么鬼呢? rt = route_lookup(&event_rt, route); event_rt是一个route_list的结构体 // route.c struct route_list event_rt; route_list的结构如下,重点是这个str_hash_table这个字段,它似乎是一个hash struct route_list { struct action **rlist; int idx; /* first empty entry */ int entries; /* total number of entries */ struct str_hash_table names; /* name to route index mappings */ }; str_hash_table的结构如下: ...
网络结构图如下: client就是客户端 PUBLIC_IP就是VPC1的公网IP VPC1和VPC2使用内网IP交互 VPC2没有公网IP client --- 1.2.3.4/udp ---> VPC1 --- 192.168.0.10/udp ---> VPC2 节点 内网IP 公网IP VPC1 192.168.0.10 1.2.3.4 VPC2 192.168.0.11 无 client通过1.2.3.4访问VPC1 VPC1因为要在后续请求中也保持在路径中,所以要做record-route
背景 多个SIP注册服务器之间,如何同步分机的注册信息呢? 简单的方案就是使用共享数据库的方式同步注册信息,这个方案实现起来简单,但是分机的注册信息本身就是个需要频繁增删改查的,数据库很可能在大量注册分机的压力下,成为性能的瓶颈。 除了数据库之外,OpenSIPS和kamailio分别提供了不同的方案。 OpenSIPS提供的方案是使用cluster模块,cluster模块在多个实例之间同步分机的注册信息,注册信息的格式是OpenSIPS自定义的格式。 Kamailio的方案是DMQ模块, DMQ听起来高大上,放佛是依赖外部的一个服务。 但它其实就是扩展SIP消息,通过SIP消息来广播分机的注册信息。 KDMQ sip:notification_peer@192.168.40.15:5090 SIP/2.0 Via: SIP/2.0/UDP 192.168.40.15;branch=z9hG4bK55e5.423d95110000 To: <sip:notification_peer@192.168.40.15:5090> From: <sip:notification_peer@192.168.40.15:5060>;tag=2cdb7a33a7f21abb98fd3a44968e3ffd-5b01 CSeq: 10 KDMQ Call-ID: 1fe138e07b5d0a7a-50419@192.168.40.15 Content-Length: 116 User-Agent: kamailio (4.3.0 (x86_64/linneaus)) Max-Forwards: 1 Content-Type: text/plain sip:192.168.40.16:5060;status=active sip:192.168.40.15:5060;status=disabled sip:192.168.40.17:5060;status=active 源码分析 该模块一共暴露了8个参数,其中7个参数都是简单类型,INT和STR,就直接取对应变量的地址就可以了。 其中notification_address参数是用来配置集群中其他节点的通信地址的,因为要配置多次,所以需要用一个函数来解析。 // dmq.c static param_export_t params[] = { {"num_workers", PARAM_INT, &dmq_num_workers}, {"ping_interval", PARAM_INT, &dmq_ping_interval}, {"server_address", PARAM_STR, &dmq_server_address}, {"server_socket", PARAM_STR, &dmq_server_socket}, {"notification_address", PARAM_STR|PARAM_USE_FUNC, dmq_add_notification_address}, {"notification_channel", PARAM_STR, &dmq_notification_channel}, {"multi_notify", PARAM_INT, &dmq_multi_notify}, {"worker_usleep", PARAM_INT, &dmq_worker_usleep}, {0, 0, 0} }; 这些参数都没有加上static关键词,主要目的为了在dmq模块的其他c文件能使用。 ...
1. 请求消息处理过程 请求可以 直接丢弃,不返回任何响应。对于恶意请求,SIP Flood攻击,最好不要返回任何响应。 直接返回状态码,不做转发,例如直接返回301重定向 无状态转发 有状态转发 执行分支路由,分支路由也可以将消息丢弃 无论有无状态,请求发出去前都会执行onsend_route路由,在onsend_route内部,已经不能对SIP消息再做拦截 2. 响应消息处理过程 首先执行reply_route{}, 在这个路由里可以将消息丢弃 然后判断消息是否有状态的 有状态,这执行onreply_route[ID]路由 如果响应是失败的,还可以执行failure_route[ID], 当前前提是在请求路由里是否设置了钩子 在失败路由可以,可以再次设置新的目标地址,进行转发; 设置了新的目标地址后,还可以设置分支路由 Tip 这里要注意的是,响应路由在失败路由之前执行。 3. 重传处理
-a mode Auto aliases mode: enable with yes or on, disable with no or off 一般都是关闭 --alias=val Add an alias, the value has to be '[proto:]hostname[:port]' (like for 'alias' global parameter) 设置对外别名, 在多个对外别名时,相比于在脚本中写死, 更好的方式 是在启动时传入, alias一般都是服务的对外域名或者IP 如果km有多个对外域名,并且不同的环境都不同,这块配置就合适在脚本里写死 --atexit=val Control atexit callbacks execution from external libraries which may access destroyed shm memory causing crash on shutdown. Can be y[es] or 1 to enable atexit callbacks, n[o] or 0 to disable, default is no. 没用过 -A define Add config pre-processor define (e.g., -A WITH_AUTH, -A 'FLT_ACC=1', -A 'DEFVAL="str-val"') 预处理的变量定义 -b nr Maximum OS UDP receive buffer size which will not be exceeded by auto-probing-and-increase procedure even if OS allows -B nr Maximum OS UDP send buffer size which will not be exceeded by auto-probing-and-increase procedure even if OS allows 这和上面的有啥区别呢? -c Check configuration file for syntax errors 可以检查配置文件的语法错误。如果这个选项开启,就只能做检查语法,而不能启动kama --cfg-print Print configuration file evaluating includes and ifdefs 在脚本里有很多预处理指令时,可以用这个参数打印出预处理之后的脚本 -d Debugging level control (multiple -d to increase the level from 0) 调试界别 --debug=val Debugging level value -D Control how daemonize is done: -D..do not fork (almost) anyway; -DD..do not daemonize creator; -DDD..daemonize (default) 控制是否开启守护进程 -e Log messages printed in terminal colors (requires -E) -E Log to stderr -f file Configuration file (default: /usr/local/etc/kamailio/kamailio.cfg) 设置配置文件的位置, 可以覆盖默认的位置 -g gid Change gid (group id) -G file Create a pgid file -h This help message --help Long option for `-h` -I Print more internal compile flags and options -K Turn on "via:" host checking when forwarding replies -l address Listen on the specified address/interface (multiple -l mean listening on more addresses). The address format is [proto:]addr_lst[:port][/advaddr][/socket_name], where proto=udp|tcp|tls|sctp, addr_lst= addr|(addr, addr_lst), addr=host|ip_address|interface_name, advaddr=addr[:port] (advertised address) and socket_name=identifying name. E.g: -l localhost, -l udp:127.0.0.1:5080, -l eth0:5062, -l udp:127.0.0.1:5080/1.2.3.4:5060, -l udp:127.0.0.1:5080//local, -l udp:127.0.0.1:5080/1.2.3.4:5060/local, -l "sctp:(eth0)", -l "(eth0, eth1, 127.0.0.1):5065". The default behaviour is to listen on all the interfaces. 控制listen的地址 --loadmodule=name load the module specified by name --log-engine=log engine name and data -L path Modules search path (default: /usr/local/lib64/kamailio/modules) -m nr Size of shared memory allocated in Megabytes 共享内存的大小设置 --modparam=modname:paramname:type:value set the module parameter type has to be 's' for string value and 'i' for int value, example: --modparam=corex:alias_subdomains:s:kamailio.org 设置模块的启动参数 对于不方便在脚本里写死的模块参数,这个方式也挺好用 --all-errors Print details about all config errors that can be detected 调试模式比较好用,打印详细的日志报错 -M nr Size of private memory allocated, in Megabytes 控制私有内存的大小 -n processes Number of child processes to fork per interface (default: 8) -N Number of tcp child processes (default: equal to `-n') -O nr Script optimization level (debugging option) -P file Create a pid file -Q Number of sctp child processes (default: equal to `-n') -r Use dns to check if is necessary to add a "received=" field to a via -R Same as `-r` but use reverse dns; (to use both use `-rR`) --server-id=num set the value for server_id --subst=exp set a subst preprocessor directive --substdef=exp set a substdef preprocessor directive --substdefs=exp set a substdefs preprocessor directive -S disable sctp -t dir Chroot to "dir" -T Disable tcp -u uid Change uid (user id) -v Version number --version Long option for `-v` -V Alternative for `-v` -x name Specify internal manager for shared memory (shm) - can be: fm, qm or tlsf -X name Specify internal manager for private memory (pkg) - if omitted, the one for shm is used -Y dir Runtime dir path -w dir Change the working directory to "dir" (default: "/") -W type poll method (depending on support in OS, it can be: poll, epoll_lt, epoll_et, sigio_rt, select, kqueue, /dev/poll)
我始终认为,快速学习一门知识,最为重要的就是熟悉它的文档结构。 对于熟悉OpenSIPS的文档结构来说的人,看了Kamailio的文档,往往觉得无从下手。 当我们打开Km的官网(kamailio.org)后, 会重定向到https://www.kamailio.org/w/, 在页面的右侧, 接着,我们会导航到 https://www.kamailio.org/w/documentation/ 这个页面的结构需要烂熟于心 1. 模块文档 如果你知道km的版本号和对应的模块,可以点击这里进入对应版本,然后再选择对应模块 2. wiki部分 2.1 必学部分 wiki部分主要是一些教程,非常值得看。 其中有三个重点部分 core cookbook: 主要讲解km的脚本的结构、全局参数、模块参数、路由类型、预处理、脚本操作等等,是编写km脚本的必学之处,各种参数都可以看看 Pseudo-Variables CookBook:主要讲解各种伪变量,用来读写SIP消息 Transformations CookBook: 主要讲解各种操作变换,例如把字符串型转为int型数据 全部函数索引 在km的wiki官网,即https://www.kamailio.org/wikidocs/, 其中Alphabetic Indexes就是所有的参数或者函数的索引。 在你学些km的脚本时,有时候想知道某个函数在哪个模块中,就可以用这个全部函数索引中查找。 源码安装 这部分讲解如何通过GIT进行源码安装。 FAQ https://www.kamailio.org/wikidocs/tutorials/faq/main/ 外部资源 这里涉及了如何用km和常见的媒体服务器如何集成 其他 官方的Web版本的wiki, 对于刚入门的人来说,还是有点绕的。 另外一个问题就是mkdocs的页面渲染效果的确不太行,目录滚动一点都不灵敏。 如果你想看的不是某个模块的说明文档,我更建议你直接把https://github.com/kamailio/kamailio-wiki 这个git仓库下载到本地查看。 总结 总体来说,km的文档算是非常完整了,但是和OpenSIPS相比,还有待加强。
核心概念 1.1 脚本结构 - 全局参数、模块配置、路由 1.2 通用元素 - 注释、标识符、变量、值、表达式 1.3 预处理指令 - 文件导入、宏定义、环境变量读取 1.4 关键词 1.5 核心值 1.6 核心参数 1.7 DNS参数 1.8 TCP参数 1.9 TLS参数 1.10 SCTP参数 1.11 UDP参数 1.12 黑名单参数 1.13 实时参数 1.14 核心函数 1.15 自定义全局参数 1.16 路由块 - 请求路由、响应路由、分支路由、失败路由等 1.17 脚本语法 - if、else、switch、while、赋值、比较、算数等 1.18 命令行参数 1.19 日志引擎