源码笔记 - 自定义事件路由(中)

[[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是路由的名,值是一个正数,正数代表了路由执行单元的索引位置。 ...

2024-12-28 09:43:00 · 2 min · Eddie Wang

源码笔记 - 自定义事件路由(上)

[[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的结构如下: ...

2024-12-27 20:45:03 · 2 min · Eddie Wang

对外地址和record-route的问题

网络结构图如下: 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

2024-12-23 19:23:43 · 1 min · Eddie Wang

DMQ模块源码学习笔记

背景 多个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文件能使用。 ...

2024-12-22 18:07:08 · 2 min · Eddie Wang

路由执行的顺序

1. 请求消息处理过程 请求可以 直接丢弃,不返回任何响应。对于恶意请求,SIP Flood攻击,最好不要返回任何响应。 直接返回状态码,不做转发,例如直接返回301重定向 无状态转发 有状态转发 执行分支路由,分支路由也可以将消息丢弃 无论有无状态,请求发出去前都会执行onsend_route路由,在onsend_route内部,已经不能对SIP消息再做拦截 2. 响应消息处理过程 首先执行reply_route{}, 在这个路由里可以将消息丢弃 然后判断消息是否有状态的 有状态,这执行onreply_route[ID]路由 如果响应是失败的,还可以执行failure_route[ID], 当前前提是在请求路由里是否设置了钩子 在失败路由可以,可以再次设置新的目标地址,进行转发; 设置了新的目标地址后,还可以设置分支路由 Tip 这里要注意的是,响应路由在失败路由之前执行。 3. 重传处理

2024-12-22 16:20:42 · 1 min · Eddie Wang

kamailio 启动参数控制

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

2024-12-22 15:46:13 · 3 min · Eddie Wang

DDOS学习笔记

攻击分类 网络层 ICMP Flood攻击: ICMP(Internet Control Message Protocol,因特网控制报文协议)是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。ICMP协议本身特点决定了它非常容易被用于攻击网络上的路由器和主机。当攻击者向目标网络发送大量的ICMP数据包时,目标主机会耗费大量的CPU资源去处理和响应,直至耗尽设备资源,无法为合法用户提供正常服务。 ARP Flood攻击: ARP(Address Resolution Protocol,地址解析协议)是用来将IP地址解析为MAC地址的协议。ARP协议主要以广播的方式发送ARP请求。同网段内的主机都可以收到广播请求,这为攻击者提供了可乘之机。攻击者通过发送大量的ARP请求,使有限的网络资源被无用的广播信息所占用,造成网络拥堵。其次,因为ARP协议没有安全认证机制,所以只要主机接收到ARP应答包,都会缓存在ARP表中,这为ARP欺骗提供了可能。 IP分片攻击: IP协议在传输数据包时,会将数据报文分为若干分片进行传输,并在目标系统中进行重组。IP分片是网络环境中经常发生的事件,但是,如果数据被人为恶意分片就会产生DDoS攻击。攻击者将经过恶意分段的数据包发送至目标网络,导致目标网络耗费大量资源进行重组,直至资源枯竭。 传输层攻击 SYN Flood攻击: SYN Flood是互联网最原始、最经典的DDoS攻击之一,主要利用了TCP协议的三次握手机制。攻击者通常利用工具或控制僵尸主机向服务器发送海量的变源IP地址或变源端口的SYN报文,服务器响应报文后产生大量的半连接,直至系统资源被耗尽,服务器无法提供正常的服务。 ACK Flood攻击: 攻击者通过僵尸网络向目标服务器发送大量的ACK报文,报文带有超大载荷,会引起链路拥塞。或向目标服务器发送极高速率的变源变端口请求,导致转发设备异常,从而引起网络瘫痪。 UDP Flood攻击: UDP Flood攻击常用于大带宽DDoS攻击。攻击者使用包含无状态UDP协议的IP数据包充塞目标主机的端口,受害主机会寻找与UDP数据包相关的应用程序。如果没有找到,就向发送者回发一条“目标不可达”消息。一旦目标主机被攻击流量淹没,系统就会失去响应,从而造成合法用户无法正常访问的现象。 应用层攻击 DNS Flood攻击: 攻击者通过操纵大量傀儡机器,对目标网络发起海量域名查询请求,以中断该域的DNS解析。这种攻击将会破坏网站、API或Web应用程序响应合法流量的能力,让合法用户无法查找到用于调用特定资源的地址,导致业务暂时中断或停止。 HTTP Flood攻击: HTTP GET 攻击:攻击者操控多台设备向目标服务器发送对图像、文件或其他资产请求,当目标服务器被传入请求和响应所淹没时,来自正常流量源的业务请求也将被拒绝。 HTTP POST 攻击:与发送 POST 请求所需的处理能力和带宽相比,处理表单数据和运行必要数据库命令的过程相对密集。这种攻击利用相对资源消耗的差异,直接向目标服务器发送大量POST请求,直至目标服务器容量饱和并拒绝服务为止。 CC攻击: CC攻击常用于攻击提供网页访问服务的服务器。攻击者通过代理服务器向目标服务器发送大量貌似合法的请求,使CPU长时间处于高负荷运行状态,永远都有处理不完的连接。攻击会导致正常访问被中止,最终宕机崩溃。 SIP注册 Flood攻击: 攻击者发送大量的SIP注册请求到SIP服务端,SIP服务器需要查询数据库,拖慢正常的数据库查询,也回占用大量的资源来维护注册的事务。 FAQ 防火墙能否拦截DDOS攻击? 拦截不了,防火墙就好比饭店的保安,保安再多,但是饭店门口道路交通堵塞了,饭店的营业额下降,再多的保安也无能为力 在遭受DDOS攻击后,用什么手段防御? 购买硬件设备:除了比较贵之外,对于使用云服务器的服务也无能为力 更换公网IP:对于使用云服务器来说,更换云服务器的公网IP看起来比较简单方便。但是也有麻烦的地方,比如自己的服务可能要涉及到配置改变和服务重启,和自己相关的第三方,也可能要修改IP的访问地址 使用云服务厂商提供的DDOS服务 如何感知到自己的服务正在遭受DDOS攻击? 异常大的流量波动 正常用户大量离线 参考 https://info.support.huawei.com/info-finder/encyclopedia/zh/DDoS%E6%94%BB%E5%87%BB.html https://www.microsoft.com/zh-cn/security/business/security-101/what-is-a-ddos-attack

2024-12-17 21:10:40 · 1 min · Eddie Wang

你不怕暴露自己的无知吗?

公开自己的错误 我在写博客时,有时候脑海里总会蹦出一个小人,面露鄙夷的脸色对我说:你写这么多没啥技术含量的垃圾,公开在网上,难道不怕暴露自己的无知吗? 说实话,我是有这样的担忧。 因为我是有自知之明的,我知道自己估计也是黄老师那种"样样通,样样松"的人。 写的东西也都是一些表面的东西,甚至有错误的可能。这并不是自谦。 我一直无法找到反击脑海里小人的理由。 今天,我在读一本书的时候,学到了一个概念,这个概念叫做坎宁安定律。 在互联网上获得正确答案的最好方法并不是提出问题,而是发布错误的答案 也许我的答案是错误的,但是它并没有被隐藏我脑海的某个角落,二是被公开在了网上。 即使我的小破站再小,必然也会有几个阅读量吧,或许能有读者对错误的答案提出自己的异议。 学习金字塔理论 如果仅仅是通过阅读学习,学习内容的平均存留率只有5%。 如果把学习内容公开,这其中就暗示了你可能需要把自己学到的内容教授给他人这一心理。 那么在记录笔记的时候,就会想办法把问题讲解的让别人更清楚,从而加深了自己的学习知识吸收。 参考 https://baike.baidu.com/item/%E5%AD%A6%E4%B9%A0%E9%87%91%E5%AD%97%E5%A1%94/9515094

2024-12-07 14:17:28 · 1 min · Eddie Wang

第一节 如何熟悉Kamailio文档的文档结构

我始终认为,快速学习一门知识,最为重要的就是熟悉它的文档结构。 对于熟悉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相比,还有待加强。

2024-12-02 22:43:24 · 1 min · Eddie Wang

TypeSctipt类型实战

2024-11-22 13:04:35 · 0 min · Eddie Wang