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 ...
写日记的正经人 - 季羡林
在电影《邪不压正》中,关于写日记的经典对白是 "正经人谁写日记啊?" "是啊" "你写日记吗?" "我不写。你写日记吗?" "谁能把心里话写日记里?" "写出来的那能叫心里话?" "下贱!" 给我灌输一种感觉写日记的人往往是虚伪的。 然而,最近我看了季羡林先生两本书《牛棚杂忆》和《留得十年》,我不得不对季先生佩服的五体投地,眼角朦胧,鼻头发酸。 我在德国十年的日记,一天不缺,恐怕有一两百万字。–《留德十年》 做一件小事,坚持10年。 这是什么样动机,是因为毅力吗? 如果说做一件事让自己感觉很难,不想做,但是做了很长时间,可以称之为毅力。 我觉得除了毅力,更多的一种喜欢,一种习惯。一种把思想自然流露指尖,落笔成文的自然天性。 我不记得以前上学时,有没有度过季先生的。然而季先生的大名,还是略有耳闻的。 但是除了季先生的大名的,其他的事情,几乎不了解。 看了两本书书后,我不禁对季先生生有有一种心驰神往。 我不知道季先生原来是清朝末年的人。经历过清末、民国、二战、解放、文化大革命… 我不知道季先生原来去德国留过学,而且是是坐火车去的。 德国哥廷根的风景描写,让我脑海里呈现一种宫崎骏动画般画面。 我不知道原来季先生在德国还经历过二战。 我不知道季先生原来还在文化大革命中,数次被打倒。 季先生的文风朴实,感情自然流露。你能从他的文字中看到一个普通人懦弱与勇气、迷茫与坚持、欲望和克制。 我喜欢季先生的文风,也喜欢季先生的为人。 我觉得,归根结底,我喜欢真实,厌恶粉饰。
RFC768 UDP包学习笔记
包格式 UDP包头工占用8个字节, 其中源端口和目标端口各占2个字节,长度占2个字节,校验和占2个字节。 0 7 8 15 16 23 24 31 +--------+--------+--------+--------+ | Source | Destination | | Port | Port | +--------+--------+--------+--------+ | | | | Length | Checksum | +--------+--------+--------+--------+ | | data octets ... +---------------- ... User Datagram Header Format 字段 源端口,可选字段,默认为0,如果是有意义的其他端口,表示后续响应可以送回到该端口 目标端口,必须字段,用来关联目标主机上进程监听的端口 长度,长度是整个UDP包的长度,也就是包头 + 包的数据,包头固定8字节,那么length的最小长度就是8 校验和,用来验证传输过程中是否发生了错误。校验和的计算结果与源IP、目标IP、UDP的包长有关。 如果校验和失败,那么消息会被丢弃。 有些校验和计算的工作会被放置在网卡上,从而减少CPU的负载。当然如果在网卡上因为校验和的问题被网卡丢弃,上层应用是收不到不到UDP包的。但是用tcpdump可以抓到这种校验和有问题的包。 思考1: 为什么源端口可以设置为0? 答:有些UDP包,是不需要响应的,只需要发送出消息。 参考 https://datatracker.ietf.org/doc/html/rfc768 https://wdd.js.org/opensips/ch7/big-udp-msg/ https://wdd.js.org/network/udp-checksum-offload/
2024年的最后三天 - 甲流来袭
28号 - 抗体 28号晚上,我觉得嗓子有点刺痛。因为前两周发过一次烧,我觉得这次嗓子痛应该不会是发烧的前奏了,毕竟身体是有抗体的。 我高估了之前产生的抗体。 29号 - 梦 从29号上午开始,身体就好像在温水煮青蛙,最高到达惊人的峰值40度。 为了控制体温,我先洗个热水澡,吃了布洛芬,躺在床上,拿了一块冰袋,裹在卫生纸里,搁在脑门上。身体挺直,裹着两层厚被。 长夜慢慢,睡睡醒醒。 我梦见自己变成了一棵树,脚上好像长出了根须,缓慢的往地下生长。穿过泥土,与蚯蚓擦肩而过。穿过岩石,被石油染黑。然而到达的却是一块冰山,冰冷坚硬。 手上长出了树干,像天空伸展,摊开自己的枝叶,还没等我享受片刻温暖,太阳仿佛飞速向我飞来,炙热的阳光让我瞬间枯黄,灰飞烟灭。 长夜慢慢,我不敢辗转反侧。我头上还顶着一块冰。 30号 - 发热门诊 终于熬到早上,再测试一遍体温,38度。 算了,还是去医院吧。 穿戴整齐,刚下楼,迎面吹来一阵冷风。我有种无法压抑的,想要咳嗽的冲动,但是我必须忍住,我知道这咳嗽必然“感天动地"。 风继续吹,我忍不住咳了出来,那感觉,仿佛有人伸手把我的气管从我的嘴巴里扯了出来。 到了医院,发热门诊是单独一层的小房子,和气派的十几层的门诊大楼相比,简直像个保安室。 发热门诊虽小,但也五脏俱全。 进门先做鼻拭子,量体温,挂号。 再做血液分析,然后就排队等叫号。医生看了报告,说我是甲流,开了两盒药,一盒抗病毒,一盒用来退烧。 从医院出来,已经中午。我走进医院旁边的永X大王,准备随便吃点。 我挑了一个座位,扫码下单,点了馄炖、蒸蛋、豆浆、银耳莲子羹。 等餐期间, 我发现对面有个中年人大叔,穿这黑色的宽大的羽绒服,胡子拉碴、头发稀少, 他时不时的巡视着其他的人。 没过过久,我等到自己的餐。 馄炖上撒了淡黄的鸡蛋丝和黑色海苔碎,鸡蛋丝吃起来像绳子,海苔碎味同嚼蜡。混度汤非常浑浊,像是用了一天的澡堂池子水。馄炖我就吃了一个,就放弃了。 蒸蛋应该是预制菜,放在黑色塑料小杯中,应该是微波炉加热的。我吃了一口,味道奇怪。 豆浆没什么好说的,毕竟也味道也没有下降空间了。 银耳莲子羹还不错,我都个喝完了。 吃饱喝足,顺便我把医生给我开的药也吃了,我起身离开,刚走到餐桌不到2米, 就瞥见那个大叔匆匆走向我的餐桌。 急诊室 我吃完饭,回到家,每隔一个小时测一次体温,体温很稳定,稳定在38度左右。 一直到晚上,我的体温还是没怎么下降。 老婆给我打电话,说让我赶紧去急诊,去输液,光吃药效果不好,高烧不退会要人命的。 但是我觉得没必要,因为尽然发热门诊的医生都没有让我输液,说明我不需要输液,或者说输液也没有多大作用。 老婆说:“你想让我中年丧夫吗?” 我无话可说,只能默默穿上衣服,带上口罩,去了早上那家医院的急诊。 晚上9点急诊室人来人往,络绎不绝,仿佛是白天的门诊。 我挂上号,接着又等了将近1个小时,终于等到我了。 给我看病的是位女医生。 ”医生,我在你们医院发生门诊早上就看过了,诊断是甲流,药也吃了,到现在还是38度,还是给我输液吧。“,我说 ”甲流不是一天两天能好的,至少要发热三天,而且输液效果也不大“,医生说 “那我也烧了两天了,再烧下去人要烧没了。你给我开个输液的吧” “好的吧,那我就给你开一次输液,你看看效果” 一顿拉扯,我终于能输液了。其实输的也不是什么特殊的东西,就是一带左氧和一袋维C。 输完液,已经晚上11点50,叫了车,回到家里。感觉嘴巴好苦,还好家里有甜的冰糖心苹果。 我啃完第一个苹果,每一口都是苦味。接着我再啃一个苹果,每一口都还是苦味。 如果不是因为发烧,我简直立即想去吃点火锅底料漱漱口。 归梦 睡觉前,我又量了一次体温。体温恢复到37度,看起来正常正常了。 我不知道这是吃药起的效果还是输液的效果。 但是两盒药的价格是270,其中一盒西药50,另一盒重要220。 输液呢,一袋左氧+维C,总共也不过才30。 让我想起了电影《大腕》的名言 31号 这是24年的最后一天,美丽的烟花在天空中绽放璀璨的光芒。 有些人觉得烟花美丽,有些人只觉得吵。
CPU眼里的C/C++
内存布局 堆和栈之前有大片的空白虚拟内存空间,用多少映射多少 堆和栈是像相反的方向增长
源码笔记 - 自定义事件路由(中)
[[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的结构如下: ...
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文件能使用。 ...
路由执行的顺序
1. 请求消息处理过程 请求可以 直接丢弃,不返回任何响应。对于恶意请求,SIP Flood攻击,最好不要返回任何响应。 直接返回状态码,不做转发,例如直接返回301重定向 无状态转发 有状态转发 执行分支路由,分支路由也可以将消息丢弃 无论有无状态,请求发出去前都会执行onsend_route路由,在onsend_route内部,已经不能对SIP消息再做拦截 2. 响应消息处理过程 首先执行reply_route{}, 在这个路由里可以将消息丢弃 然后判断消息是否有状态的 有状态,这执行onreply_route[ID]路由 如果响应是失败的,还可以执行failure_route[ID], 当前前提是在请求路由里是否设置了钩子 在失败路由可以,可以再次设置新的目标地址,进行转发; 设置了新的目标地址后,还可以设置分支路由 Tip 这里要注意的是,响应路由在失败路由之前执行。 3. 重传处理
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)