HEP 协议学习笔记

HEP简介 HEP协议目前叫做EEP(Extensible Encapsulation Protocol), 那之前的缩写HEP的H,我只能推测为homer。 这个协议的主要功能是对VoIP连路上的数据包,例如SIP进行封装,方便后续分析SIP信令图。 目前这个协议已经升级到V3版本,在这个pdfHEP3_Network_Protocol_Specification_REV_36中有详细的介绍。 今天我们主要看这个协议的V3版本的协议是如何实现的。 包头 packet-beta title HEP Packet 0-3: "版本号" 4-5: "总长度" 6-15: "数据段(变长)" HEPv3的包头是6个字节,主要分为三个部分 版本号:固定4个字节长度,是HEP3 总长度:固定2个字节长度,是包的总长度,这个总长度包括了包头的六个字节。所以HEP包的大小范围一般是6-65535之间。 数据段:数据段的长度不固定 注意事项:假如从传输层读到1000个字节的数据,在解析前6个字节是,发现总长度(total length)的子段是1200,那就说明本次读到的数据还不是一个完整的 数据段解析 数据段由固定6字节长度的头部和变长的payload部分。 packet-beta title HEP Packet 0-1: "vendor ID" 2-3: "type ID" 4-5: "length" 6-15: "payload" vendor ID: 固定2字节长度, 其实意义不大, type ID: 固定2字节长度,这个子段很重要,决定了payload的类型。可以理解为是一个对象的key, 然后把payload理解为value length: 固定2字节长度,范围是0-65535,这个字段是也是整个数据段的长度,也就是包括了6个字节的段头 payload: 长度是length的长度-6,表示数据长度 hep协议有个type ID的映射表 chunk type ID 类型 描述 0x0001 uint8 IP类型,0x02=IPv4, 0x0a=IPv6 0x0002 uint8 协议类型 0x06=TCP, 0x11=UDP, 可以参考IP协议号列表 其他的字段还有很多,可以参考HEP3_Network_Protocol_Specification_REV_36 ...

2025-02-02 15:12:40 · 1 分钟 · Eddie Wang

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

[[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 分钟 · 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 分钟 · 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 分钟 · 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 分钟 · 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 分钟 · 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 分钟 · Eddie Wang

第二份工作的辞职总结

总结 第二份工作快到一年了,我最近申请离职了。 同事问我离职原因,我说被领导骂了,感觉自尊受辱,索性不干了。 实际上被领导骂只是压死骆驼的最后一根稻草,我也不是那种会心血来潮说干就不干的。 这份工作,我做了不到一年,大部分的时间我是做的不开心的。 所以离职是一直在心里酝酿。 为什么会工作不开心? 主要原因有以下四点: 行业前景暗淡:S公司处在行业的中游,上游受到运营商管控,下游在客户层面话语权也不大。这是一个资源限制型的行业,行业的命脉就在运营商,可能运营商有个风吹草动,整个行业就要翻江倒海。 自研能力薄弱:S公司大部分业务都是购买的第三方的软件,各种变更都受到制约,添加一个小功能都要付费升级。自研能力弱的一个原因可能就是前些年赚钱太容易了,投资研发哪有赚快钱来的舒服呢。同时也是这个原因,多个第三方软件之间也造成了数据割裂,运行维护困难。 家族企业制约:S公司大部分人员都是老板的亲朋好友,家族式企业的优点是内聚力强,但是包容性弱。之前也有一些大厂进来的员工,没多久就辞职了。 我曾经也跟老板沟通过,似乎老板也有心做些变革。但是变革哪有那么容易,要变革不早都变了,还要等到现在? 短期主义:似乎S公司没有什么具体的愿景,唯一感觉到的愿景就是赚快钱。一旦发现投入的回报太少,就立即切换目标。 企业关怀差:说实在,我没有感觉到多少员工关怀。在S公司的工作和在地主家做短工也没啥区别,老板想什么都要管,但是即管不了那么多,又管的不好。所以给人的感觉就是东一棒槌,西一榔头,看起来很卖力,实际上并没有什么产出。几乎每个月都有人离职,对员工都吝啬,不考虑员工的自尊,员工怎么可能对公司有归属感。 工作压力大:受限于资源收紧、产品优势小,老板想提高营业额的方法是提高员工的服务水平。说实在,这真是杯水车薪。不想着从底层系统的解决问题,只想着通过给员工压力、通过加班来解决问题,只会造成工作压力大、人员流失大。 所以,我的离职,实际上也是考虑再三的一个选择。 当然,我在S公司也不是什么收获都没有。 收获总结 深入了解了行业的运行原理、客户、资源、政策等信息 对于数据分析方面有了较大的进步,毕竟我做了40+个数据报表来分析运行数据 深入学习了python, 主要用来做数据分析。数据分析方面的协议栈主要有pandas、duckdb、doris(MySQL)、grafana来组成 深入学习了python的GUI开发,主要用到的框架是wxPython,开发的两个工具分别给到运维人员和客服去使用,来提高运行效率

2024-09-10 21:08:51 · 1 分钟 · Eddie Wang

解决 VsCode pwsh终端环境变量不生效问题

我在系统的环境变量设置里,用户和系统中,都加入了ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/这个环境变量。 单独打开windows Terminal应用,使用 env | grep ELE 是能搜到我设置的环境变量的。但是在vscode中,这个环境变量不存在。 我尝试了以下几个方法 重启电脑,无效 配置"terminal.integrated.persistentSessionReviveProcess": "never", 然后重启电脑,无效 在终端直接执行code ., 在终端打开vscode, 依然无效 因此,我想起了之前配置的pwsh的配置文件。 可以使用code $PROFILE, 打开pwsh的配置文件。 然后再配置文件中设置环境变量, 之后重启vscode, 环境变量就正常能读取到了。 # 文件名 Microsoft.PowerShell_profile.ps1 $env:ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/" 参考 https://www.zhihu.com/question/266858097

2023-11-16 08:08:39 · 1 分钟 · Eddie Wang

如何选择,汉王Clear7与文石Leaf3

购买选择 我的kindle pw4已经用了快三年了,机器性能有些不足,电池也有些不够用了。正逢双十一之际,我觉得可以买一个开放系统的电纸书来用用。 买开放系统的另一个原因是自从亚马逊退出中国后,我用微信读书比较多,也买了微信读书的会员。 开放系统基本上都是基于安卓系统定制的,可以选择的厂商比较多。 最终筛选下来,我选择了汉王clear7和文石leaf3, 我下单了两个,觉得先体验一下,然后在退一个。 然而用了不到两天之后,我还是决定把两个机器都给退了,继续用我的kindle pw4。 选择因素 就我个人而言,电子书最为重要的选择因素有以下三点。 显示效果: 说实在的,现在的电子书基本上都是carta1200、300ppi、冷暖光。基本上从显示效果上来说,各大厂家都是半斤八两。就我自己体验而言,我并没有发现汉王的显示效果和文石leaf3有何不同。 握持手感: 外观影响握持手感,外观一般分为两类,一类是就是平板设计,像kindle paperwhite系列。另一类就是书脊式设计,握持更舒适,像kindle os3, 汉王clear7之类的。 简洁系统: 电子书并不是手机,不需要太多的功能,功能太多反而成为累赘 稳定性: 点子产品难免被摔、或者淋水,对于这些意外情况,如果产品非常脆弱,那么很大可能会损失大笔的金额。 续航 文石的缺点 文石和汉王的两款产品,拿到手开机不到一小时,我就决定先退了文石。 原因有以下几点 显示效果文石和汉王差不多,没啥突出亮点 握持手感文石肯定比不过汉王的书脊式设计, 并且汉王更轻 文石的系统太复杂,设置太多,甚至还有个iphone类似的小白点设计,小白点点击之后展开很多图标,每个图标我都不知道做什么的 掉电速度,文石的电量肉眼可见的在不断减少 汉王的缺点 微信读书功能阉割, 这不是汉王的问题,而是微信读书自身的问题。例如书架页面,书籍只有一个排序的顺序,就是按照最近阅读的排序。而无法像ios或者安卓app一样能够按照阅读进度排序,另外也没有阅读时长分析的功能。 另外我也注意到,微信读书电纸书版,扫码登录的有效期是24小时,我不知道这是不是意味着24小时之后我又要重新扫码。 触感光滑,汉王的背板的触感让人感觉非常光滑,感觉稍不注意就会滑下去的样子。完全没有kindle pw4那种拿在手机,就粘在手上的感觉。所以我觉得,买汉王,就必须要买一个保护套,但是官方卖的保护套也太贵了吧,居然要110多块,这点有点像割韭菜。什么保护套能卖那么贵,都快赶上十分之一的电纸书的价格了。 词典缺陷,汉王的词典也是我要吐槽的功能之一,只能把单词的原型加入到单词本,单词来个复数或者过去分词,就不能加入单词本了。kindle的生词本,所有单词,无论什么形式都能加入单词本。 掉电速度, 从昨晚五点多开机,到今天早上,基本上没到一天,电池已经掉电接近40%了 自动关机, 为了省电,汉王在2小时不使用后,会自动关机。然而这个开机时间就有点久,你可设想一下,每天早上,你想看书的时候,你都要等几十秒的开机时间,那你能受得了吗? 按钮反馈,松松垮垮, 触感没有一致性体验 结论 目前而言,国产的电纸书系统,除了开放的系统之外,其他各个方面还是存在与kindle有较大的差距。 当然,如果你从未使用过kindle系列的产品,国产的电纸书还是值得买的。

2023-11-06 09:30:34 · 1 分钟 · Eddie Wang